lds.template.yaml 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994
  1. resources:
  2. - '@type': type.googleapis.com/envoy.config.listener.v3.Listener
  3. name: briven
  4. per_connection_buffer_limit_bytes: 32768 # 32 KiB
  5. address:
  6. socket_address:
  7. address: 0.0.0.0
  8. port_value: 8000
  9. filter_chains:
  10. - filters:
  11. - name: envoy.filters.network.http_connection_manager
  12. typed_config:
  13. '@type': >-
  14. type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
  15. stat_prefix: ingress_http
  16. normalize_path: true
  17. merge_slashes: true
  18. path_with_escaped_slashes_action: REJECT_REQUEST
  19. use_remote_address: true
  20. common_http_protocol_options:
  21. headers_with_underscores_action: REJECT_REQUEST
  22. upgrade_configs:
  23. - upgrade_type: websocket
  24. access_log:
  25. - name: envoy.access_loggers.stdout
  26. typed_config:
  27. '@type': >-
  28. type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
  29. log_format:
  30. text_format_source:
  31. inline_string: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% - - [%START_TIME(%d/%b/%Y:%H:%M:%S %z)%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %BYTES_SENT% \"%REQ(REFERER)%\" \"%REQ(USER-AGENT)%\"\n"
  32. route_config:
  33. name: briven_route
  34. virtual_hosts:
  35. - name: briven_host
  36. domains:
  37. - '*'
  38. cors:
  39. allow_origin_string_match:
  40. - safe_regex:
  41. regex: ".*"
  42. allow_methods: "GET,POST,PUT,PATCH,DELETE,OPTIONS,HEAD,CONNECT,TRACE"
  43. allow_headers: "*"
  44. expose_headers: "*"
  45. max_age: "3600"
  46. request_headers_to_add:
  47. - header:
  48. key: X-Forwarded-Host
  49. value: "%REQ(:AUTHORITY)%"
  50. append_action: ADD_IF_ABSENT
  51. - header:
  52. key: X-Forwarded-Port
  53. value: "%DOWNSTREAM_LOCAL_PORT%"
  54. append_action: ADD_IF_ABSENT
  55. routes:
  56. - match:
  57. prefix: /auth/v1/verify
  58. route:
  59. cluster: auth
  60. prefix_rewrite: /verify
  61. timeout: 30s
  62. request_headers_to_add:
  63. - header:
  64. key: X-Forwarded-Prefix
  65. value: /auth/v1/verify
  66. append_action: ADD_IF_ABSENT
  67. typed_per_filter_config:
  68. envoy.filters.http.basic_auth:
  69. '@type': >-
  70. type.googleapis.com/envoy.config.route.v3.FilterConfig
  71. disabled: true
  72. envoy.filters.http.rbac:
  73. '@type': >-
  74. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  75. rbac:
  76. rules:
  77. action: ALLOW
  78. policies:
  79. allow_all:
  80. permissions:
  81. - any: true
  82. principals:
  83. - any: true
  84. - match:
  85. prefix: /auth/v1/callback
  86. route:
  87. cluster: auth
  88. prefix_rewrite: /callback
  89. timeout: 30s
  90. request_headers_to_add:
  91. - header:
  92. key: X-Forwarded-Prefix
  93. value: /auth/v1/callback
  94. append_action: ADD_IF_ABSENT
  95. typed_per_filter_config:
  96. envoy.filters.http.basic_auth:
  97. '@type': >-
  98. type.googleapis.com/envoy.config.route.v3.FilterConfig
  99. disabled: true
  100. envoy.filters.http.rbac:
  101. '@type': >-
  102. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  103. rbac:
  104. rules:
  105. action: ALLOW
  106. policies:
  107. allow_all:
  108. permissions:
  109. - any: true
  110. principals:
  111. - any: true
  112. - match:
  113. prefix: /auth/v1/authorize
  114. route:
  115. cluster: auth
  116. prefix_rewrite: /authorize
  117. timeout: 30s
  118. request_headers_to_add:
  119. - header:
  120. key: X-Forwarded-Prefix
  121. value: /auth/v1/authorize
  122. append_action: ADD_IF_ABSENT
  123. typed_per_filter_config:
  124. envoy.filters.http.basic_auth:
  125. '@type': >-
  126. type.googleapis.com/envoy.config.route.v3.FilterConfig
  127. disabled: true
  128. envoy.filters.http.rbac:
  129. '@type': >-
  130. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  131. rbac:
  132. rules:
  133. action: ALLOW
  134. policies:
  135. allow_all:
  136. permissions:
  137. - any: true
  138. principals:
  139. - any: true
  140. - match:
  141. prefix: /auth/v1/.well-known/jwks.json
  142. route:
  143. cluster: auth
  144. prefix_rewrite: /.well-known/jwks.json
  145. timeout: 30s
  146. request_headers_to_add:
  147. - header:
  148. key: X-Forwarded-Prefix
  149. value: /auth/v1/.well-known/jwks.json
  150. append_action: ADD_IF_ABSENT
  151. typed_per_filter_config:
  152. envoy.filters.http.basic_auth:
  153. '@type': >-
  154. type.googleapis.com/envoy.config.route.v3.FilterConfig
  155. disabled: true
  156. envoy.filters.http.rbac:
  157. '@type': >-
  158. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  159. rbac:
  160. rules:
  161. action: ALLOW
  162. policies:
  163. allow_all:
  164. permissions:
  165. - any: true
  166. principals:
  167. - any: true
  168. - match:
  169. prefix: /.well-known/oauth-authorization-server
  170. route:
  171. cluster: auth
  172. timeout: 30s
  173. request_headers_to_add:
  174. - header:
  175. key: X-Forwarded-Prefix
  176. value: /.well-known/oauth-authorization-server
  177. append_action: ADD_IF_ABSENT
  178. typed_per_filter_config:
  179. envoy.filters.http.basic_auth:
  180. '@type': >-
  181. type.googleapis.com/envoy.config.route.v3.FilterConfig
  182. disabled: true
  183. envoy.filters.http.rbac:
  184. '@type': >-
  185. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  186. rbac:
  187. rules:
  188. action: ALLOW
  189. policies:
  190. allow_all:
  191. permissions:
  192. - any: true
  193. principals:
  194. - any: true
  195. - match:
  196. prefix: /sso/saml/acs
  197. route:
  198. cluster: auth
  199. timeout: 30s
  200. request_headers_to_add:
  201. - header:
  202. key: X-Forwarded-Prefix
  203. value: /sso/saml/acs
  204. append_action: ADD_IF_ABSENT
  205. typed_per_filter_config:
  206. envoy.filters.http.basic_auth:
  207. '@type': >-
  208. type.googleapis.com/envoy.config.route.v3.FilterConfig
  209. disabled: true
  210. envoy.filters.http.rbac:
  211. '@type': >-
  212. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  213. rbac:
  214. rules:
  215. action: ALLOW
  216. policies:
  217. allow_all:
  218. permissions:
  219. - any: true
  220. principals:
  221. - any: true
  222. - match:
  223. prefix: /sso/saml/metadata
  224. route:
  225. cluster: auth
  226. timeout: 30s
  227. request_headers_to_add:
  228. - header:
  229. key: X-Forwarded-Prefix
  230. value: /sso/saml/metadata
  231. append_action: ADD_IF_ABSENT
  232. typed_per_filter_config:
  233. envoy.filters.http.basic_auth:
  234. '@type': >-
  235. type.googleapis.com/envoy.config.route.v3.FilterConfig
  236. disabled: true
  237. envoy.filters.http.rbac:
  238. '@type': >-
  239. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  240. rbac:
  241. rules:
  242. action: ALLOW
  243. policies:
  244. allow_all:
  245. permissions:
  246. - any: true
  247. principals:
  248. - any: true
  249. - name: functions-v1-all
  250. match:
  251. prefix: /functions/v1/
  252. route:
  253. cluster: functions
  254. prefix_rewrite: /
  255. timeout: 150s
  256. request_headers_to_add:
  257. - header:
  258. key: X-Forwarded-Prefix
  259. value: /functions/v1/
  260. append_action: ADD_IF_ABSENT
  261. typed_per_filter_config:
  262. envoy.filters.http.basic_auth:
  263. '@type': >-
  264. type.googleapis.com/envoy.config.route.v3.FilterConfig
  265. disabled: true
  266. envoy.filters.http.rbac:
  267. '@type': >-
  268. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  269. rbac:
  270. rules:
  271. action: ALLOW
  272. policies:
  273. allow_all:
  274. permissions:
  275. - any: true
  276. principals:
  277. - any: true
  278. - match:
  279. prefix: /storage/v1/
  280. route:
  281. cluster: storage
  282. prefix_rewrite: /
  283. timeout: 30s
  284. request_headers_to_add:
  285. - header:
  286. key: X-Forwarded-Prefix
  287. value: /storage/v1
  288. append_action: ADD_IF_ABSENT
  289. typed_per_filter_config:
  290. envoy.filters.http.basic_auth:
  291. '@type': >-
  292. type.googleapis.com/envoy.config.route.v3.FilterConfig
  293. disabled: true
  294. envoy.filters.http.rbac:
  295. '@type': >-
  296. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  297. rbac:
  298. rules:
  299. action: ALLOW
  300. policies:
  301. allow_all:
  302. permissions:
  303. - any: true
  304. principals:
  305. - any: true
  306. - name: auth-v1-protected
  307. match:
  308. prefix: /auth/v1/
  309. route:
  310. cluster: auth
  311. prefix_rewrite: /
  312. timeout: 30s
  313. request_headers_to_add:
  314. - header:
  315. key: X-Forwarded-Prefix
  316. value: /auth/v1/
  317. append_action: ADD_IF_ABSENT
  318. typed_per_filter_config:
  319. envoy.filters.http.basic_auth:
  320. '@type': >-
  321. type.googleapis.com/envoy.config.route.v3.FilterConfig
  322. disabled: true
  323. - name: rest-v1-protected
  324. match:
  325. prefix: /rest/v1/
  326. route:
  327. cluster: rest
  328. prefix_rewrite: /
  329. timeout: 30s
  330. request_headers_to_add:
  331. - header:
  332. key: X-Forwarded-Prefix
  333. value: /rest/v1/
  334. append_action: ADD_IF_ABSENT
  335. typed_per_filter_config:
  336. envoy.filters.http.basic_auth:
  337. '@type': >-
  338. type.googleapis.com/envoy.config.route.v3.FilterConfig
  339. disabled: true
  340. - name: graphql-v1-protected
  341. match:
  342. prefix: /graphql/v1
  343. route:
  344. cluster: rest
  345. prefix_rewrite: /rpc/graphql
  346. timeout: 30s
  347. request_headers_to_add:
  348. - header:
  349. key: X-Forwarded-Prefix
  350. value: /graphql/v1
  351. append_action: ADD_IF_ABSENT
  352. - header:
  353. key: Content-Profile
  354. value: graphql_public
  355. append_action: ADD_IF_ABSENT
  356. typed_per_filter_config:
  357. envoy.filters.http.basic_auth:
  358. '@type': >-
  359. type.googleapis.com/envoy.config.route.v3.FilterConfig
  360. disabled: true
  361. - name: realtime-v1-api-protected
  362. match:
  363. prefix: /realtime/v1/api
  364. route:
  365. cluster: realtime
  366. prefix_rewrite: /api
  367. timeout: 30s
  368. host_rewrite_literal: realtime-dev.briven-realtime
  369. request_headers_to_add:
  370. - header:
  371. key: X-Forwarded-Prefix
  372. value: /realtime/v1/api
  373. append_action: ADD_IF_ABSENT
  374. typed_per_filter_config:
  375. envoy.filters.http.basic_auth:
  376. '@type': >-
  377. type.googleapis.com/envoy.config.route.v3.FilterConfig
  378. disabled: true
  379. - name: realtime-v1-ws-protected
  380. match:
  381. prefix: /realtime/v1/
  382. route:
  383. cluster: realtime
  384. prefix_rewrite: /socket/
  385. timeout: 30s
  386. host_rewrite_literal: realtime-dev.briven-realtime
  387. request_headers_to_add:
  388. - header:
  389. key: X-Forwarded-Prefix
  390. value: /realtime/v1/
  391. append_action: ADD_IF_ABSENT
  392. typed_per_filter_config:
  393. envoy.filters.http.basic_auth:
  394. '@type': >-
  395. type.googleapis.com/envoy.config.route.v3.FilterConfig
  396. disabled: true
  397. - name: pg-protected
  398. match:
  399. prefix: /pg/
  400. route:
  401. cluster: meta
  402. prefix_rewrite: /
  403. timeout: 30s
  404. request_headers_to_add:
  405. - header:
  406. key: X-Forwarded-Prefix
  407. value: /pg/
  408. append_action: ADD_IF_ABSENT
  409. typed_per_filter_config:
  410. envoy.filters.http.basic_auth:
  411. '@type': >-
  412. type.googleapis.com/envoy.config.route.v3.FilterConfig
  413. disabled: true
  414. - match:
  415. prefix: /api/mcp
  416. route:
  417. cluster: studio
  418. timeout: 30s
  419. request_headers_to_add:
  420. - header:
  421. key: X-Forwarded-Prefix
  422. value: /api/mcp
  423. append_action: ADD_IF_ABSENT
  424. typed_per_filter_config:
  425. envoy.filters.http.basic_auth:
  426. '@type': >-
  427. type.googleapis.com/envoy.config.route.v3.FilterConfig
  428. disabled: true
  429. envoy.filters.http.rbac:
  430. '@type': >-
  431. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  432. rbac:
  433. rules:
  434. action: DENY
  435. policies:
  436. deny_all:
  437. permissions:
  438. - any: true
  439. principals:
  440. - any: true
  441. - match:
  442. prefix: /mcp
  443. route:
  444. cluster: studio
  445. prefix_rewrite: /api/mcp
  446. timeout: 30s
  447. request_headers_to_add:
  448. - header:
  449. key: X-Forwarded-Prefix
  450. value: /mcp
  451. append_action: ADD_IF_ABSENT
  452. typed_per_filter_config:
  453. envoy.filters.http.basic_auth:
  454. '@type': >-
  455. type.googleapis.com/envoy.config.route.v3.FilterConfig
  456. disabled: true
  457. envoy.filters.http.rbac:
  458. '@type': >-
  459. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  460. # Block access to /mcp by default
  461. rbac:
  462. rules:
  463. action: DENY
  464. policies:
  465. deny_all:
  466. permissions:
  467. - any: true
  468. principals:
  469. - any: true
  470. # Enable local access (danger zone!)
  471. # 1. Comment out the 'rbac' block above.
  472. # 2. Uncomment and adjust the 'rbac' block below.
  473. # 3. Add or adjust your local IPs in 'principals'.
  474. #rbac:
  475. # rules:
  476. # action: ALLOW
  477. # policies:
  478. # allow_local:
  479. # permissions:
  480. # - any: true
  481. # principals:
  482. # - direct_remote_ip:
  483. # address_prefix: 127.0.0.1
  484. # prefix_len: 32
  485. # - direct_remote_ip:
  486. # address_prefix: ::1
  487. # prefix_len: 128
  488. - match:
  489. prefix: /
  490. route:
  491. cluster: studio
  492. timeout: 30s
  493. request_headers_to_remove:
  494. - authorization
  495. request_headers_to_add:
  496. - header:
  497. key: X-Forwarded-Prefix
  498. value: /
  499. append_action: ADD_IF_ABSENT
  500. typed_per_filter_config:
  501. envoy.filters.http.rbac:
  502. '@type': >-
  503. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute
  504. rbac:
  505. rules:
  506. action: ALLOW
  507. policies:
  508. allow_all:
  509. permissions:
  510. - any: true
  511. principals:
  512. - any: true
  513. http_filters:
  514. - name: envoy.filters.http.cors
  515. typed_config:
  516. '@type': >-
  517. type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
  518. - name: envoy.filters.http.basic_auth
  519. typed_config:
  520. '@type': >-
  521. type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth
  522. users:
  523. inline_string: '${DASHBOARD_BASIC_AUTH}'
  524. # Copies ?apikey=... from the URL into the apikey header when clients omit the header.
  525. - name: envoy.filters.http.lua
  526. typed_config:
  527. '@type': >-
  528. type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
  529. inline_code: |
  530. local FUNCTIONS_ROUTE = "functions-v1-all"
  531. local FUNCTIONS_PREFIX = "/functions/v1/"
  532. local function is_functions_request(request_handle, headers)
  533. if request_handle:streamInfo():routeName() == FUNCTIONS_ROUTE then
  534. return true
  535. end
  536. local path = headers:get(":path")
  537. if path == nil then
  538. return false
  539. end
  540. return string.sub(path, 1, string.len(FUNCTIONS_PREFIX)) == FUNCTIONS_PREFIX
  541. end
  542. function envoy_on_request(request_handle)
  543. local headers = request_handle:headers()
  544. if is_functions_request(request_handle, headers) then
  545. return
  546. end
  547. if headers:get("apikey") ~= nil then
  548. return
  549. end
  550. local path = headers:get(":path")
  551. local query_start = string.find(path, "?", 1, true)
  552. if query_start == nil then
  553. return
  554. end
  555. local query = string.sub(path, query_start + 1)
  556. for key, value in string.gmatch(query, "([^&]+)=([^&]*)") do
  557. if key == "apikey" and value ~= "" then
  558. headers:add("apikey", value)
  559. return
  560. end
  561. end
  562. end
  563. # Translates the query parameter apikey into the matching internal JWT and rewrites the URL so only JWTs propagate downstream.
  564. - name: envoy.filters.http.lua
  565. typed_config:
  566. '@type': >-
  567. type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
  568. inline_code: |
  569. local FUNCTIONS_ROUTE = "functions-v1-all"
  570. local FUNCTIONS_PREFIX = "/functions/v1/"
  571. local SECRET_KEY = "${BRIVEN_SECRET_KEY}"
  572. local PUBLISHABLE_KEY = "${BRIVEN_PUBLISHABLE_KEY}"
  573. local SERVICE_ROLE_JWT = "${SERVICE_ROLE_KEY_ASYMMETRIC}"
  574. local ANON_JWT = "${ANON_KEY_ASYMMETRIC}"
  575. local TRANSLATION_ENABLED = SECRET_KEY ~= "" and PUBLISHABLE_KEY ~= "" and SERVICE_ROLE_JWT ~= "" and ANON_JWT ~= ""
  576. local function is_functions_request(request_handle, headers)
  577. if request_handle:streamInfo():routeName() == FUNCTIONS_ROUTE then
  578. return true
  579. end
  580. local path = headers:get(":path")
  581. if path == nil then
  582. return false
  583. end
  584. return string.sub(path, 1, string.len(FUNCTIONS_PREFIX)) == FUNCTIONS_PREFIX
  585. end
  586. local function translate_apikey(apikey)
  587. if apikey == nil or apikey == "" then
  588. return nil
  589. end
  590. if not TRANSLATION_ENABLED then
  591. return nil
  592. end
  593. if apikey == SECRET_KEY then
  594. return SERVICE_ROLE_JWT
  595. end
  596. if apikey == PUBLISHABLE_KEY then
  597. return ANON_JWT
  598. end
  599. return nil
  600. end
  601. local function extract_query_apikey(path)
  602. if path == nil or path == "" then
  603. return nil
  604. end
  605. local query_start = string.find(path, "?", 1, true)
  606. if query_start == nil then
  607. return nil
  608. end
  609. local query = string.sub(path, query_start + 1)
  610. for key, value in string.gmatch(query, "([^&]+)=([^&]*)") do
  611. if key == "apikey" and value ~= "" then
  612. return value
  613. end
  614. end
  615. return nil
  616. end
  617. local function replace_query_apikey(path, new_value)
  618. if path == nil or path == "" or new_value == nil or new_value == "" then
  619. return nil
  620. end
  621. local query_start = string.find(path, "?", 1, true)
  622. if query_start == nil then
  623. return nil
  624. end
  625. local base = string.sub(path, 1, query_start)
  626. local query = string.sub(path, query_start + 1)
  627. local updated = {}
  628. local replaced = false
  629. for part in string.gmatch(query, "([^&]+)") do
  630. local key, value = string.match(part, "([^=]+)=(.*)")
  631. if key == "apikey" then
  632. part = key .. "=" .. new_value
  633. replaced = true
  634. end
  635. table.insert(updated, part)
  636. end
  637. if not replaced then
  638. return nil
  639. end
  640. return base .. table.concat(updated, "&")
  641. end
  642. function envoy_on_request(request_handle)
  643. local headers = request_handle:headers()
  644. if is_functions_request(request_handle, headers) then
  645. return
  646. end
  647. local path = headers:get(":path")
  648. local apikey = extract_query_apikey(path)
  649. local translated = translate_apikey(apikey)
  650. if translated == nil then
  651. return
  652. end
  653. headers:replace("apikey", translated)
  654. local rewritten_path = replace_query_apikey(path, translated)
  655. if rewritten_path ~= nil then
  656. headers:replace(":path", rewritten_path)
  657. end
  658. end
  659. # Translates an apikey header into the appropriate internal JWT for downstream RBAC checks.
  660. - name: envoy.filters.http.lua
  661. typed_config:
  662. '@type': >-
  663. type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
  664. inline_code: |
  665. local FUNCTIONS_ROUTE = "functions-v1-all"
  666. local FUNCTIONS_PREFIX = "/functions/v1/"
  667. local SECRET_KEY = "${BRIVEN_SECRET_KEY}"
  668. local PUBLISHABLE_KEY = "${BRIVEN_PUBLISHABLE_KEY}"
  669. local SERVICE_ROLE_JWT = "${SERVICE_ROLE_KEY_ASYMMETRIC}"
  670. local ANON_JWT = "${ANON_KEY_ASYMMETRIC}"
  671. local TRANSLATION_ENABLED = SECRET_KEY ~= "" and PUBLISHABLE_KEY ~= "" and SERVICE_ROLE_JWT ~= "" and ANON_JWT ~= ""
  672. local function is_functions_request(request_handle, headers)
  673. if request_handle:streamInfo():routeName() == FUNCTIONS_ROUTE then
  674. return true
  675. end
  676. local path = headers:get(":path")
  677. if path == nil then
  678. return false
  679. end
  680. return string.sub(path, 1, string.len(FUNCTIONS_PREFIX)) == FUNCTIONS_PREFIX
  681. end
  682. local function translate_apikey(apikey)
  683. if apikey == nil or apikey == "" then
  684. return nil
  685. end
  686. if not TRANSLATION_ENABLED then
  687. return nil
  688. end
  689. if apikey == SECRET_KEY then
  690. return SERVICE_ROLE_JWT
  691. end
  692. if apikey == PUBLISHABLE_KEY then
  693. return ANON_JWT
  694. end
  695. return nil
  696. end
  697. function envoy_on_request(request_handle)
  698. local headers = request_handle:headers()
  699. if is_functions_request(request_handle, headers) then
  700. return
  701. end
  702. local translated = translate_apikey(headers:get("apikey"))
  703. if translated ~= nil and translated ~= "" then
  704. headers:replace("apikey", translated)
  705. end
  706. end
  707. # Mirrors apikey into x-api-key for realtime WS compatibility.
  708. - name: envoy.filters.http.lua
  709. typed_config:
  710. '@type': >-
  711. type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
  712. inline_code: |
  713. local REALTIME_WS_ROUTE = "realtime-v1-ws-protected"
  714. function envoy_on_request(request_handle)
  715. local route_name = request_handle:streamInfo():routeName()
  716. if route_name ~= REALTIME_WS_ROUTE then
  717. return
  718. end
  719. local headers = request_handle:headers()
  720. local apikey = headers:get("apikey")
  721. if apikey == nil or apikey == "" then
  722. return
  723. end
  724. headers:replace("x-api-key", apikey)
  725. end
  726. # Synthesizes an Authorization header (Bearer …) from apikey when callers don’t provide a real JWT header.
  727. - name: envoy.filters.http.lua
  728. typed_config:
  729. '@type': >-
  730. type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
  731. inline_code: |
  732. local FUNCTIONS_ROUTE = "functions-v1-all"
  733. local FUNCTIONS_PREFIX = "/functions/v1/"
  734. local REALTIME_WS_ROUTE = "realtime-v1-ws-protected"
  735. local function is_functions_request(request_handle, headers)
  736. if request_handle:streamInfo():routeName() == FUNCTIONS_ROUTE then
  737. return true
  738. end
  739. local path = headers:get(":path")
  740. if path == nil then
  741. return false
  742. end
  743. return string.sub(path, 1, string.len(FUNCTIONS_PREFIX)) == FUNCTIONS_PREFIX
  744. end
  745. local function has_real_jwt(auth_header)
  746. if auth_header == nil or auth_header == "" then
  747. return false
  748. end
  749. if string.sub(auth_header, 1, 7) ~= "Bearer " then
  750. return false
  751. end
  752. return string.sub(auth_header, 1, 10) ~= "Bearer sb_"
  753. end
  754. local function format_authorization(value)
  755. if value == nil or value == "" then
  756. return nil
  757. end
  758. if string.sub(value, 1, 7) == "Bearer " then
  759. return value
  760. end
  761. return "Bearer " .. value
  762. end
  763. function envoy_on_request(request_handle)
  764. local headers = request_handle:headers()
  765. if request_handle:streamInfo():routeName() == REALTIME_WS_ROUTE then
  766. return
  767. end
  768. if is_functions_request(request_handle, headers) then
  769. return
  770. end
  771. if has_real_jwt(headers:get("authorization")) then
  772. return
  773. end
  774. local apikey = headers:get("apikey")
  775. local authorization_value = format_authorization(apikey)
  776. if authorization_value ~= nil then
  777. headers:replace("authorization", authorization_value)
  778. end
  779. end
  780. # Returns 401 for missing/invalid API keys on protected API routes.
  781. - name: envoy.filters.http.lua
  782. typed_config:
  783. '@type': >-
  784. type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua
  785. inline_code: |
  786. local ANON_KEY = "${ANON_KEY}"
  787. local SERVICE_ROLE_KEY = "${SERVICE_ROLE_KEY}"
  788. local BRIVEN_PUBLISHABLE_KEY = "${BRIVEN_PUBLISHABLE_KEY}"
  789. local BRIVEN_SECRET_KEY = "${BRIVEN_SECRET_KEY}"
  790. local ANON_KEY_ASYMMETRIC = "${ANON_KEY_ASYMMETRIC}"
  791. local SERVICE_ROLE_KEY_ASYMMETRIC = "${SERVICE_ROLE_KEY_ASYMMETRIC}"
  792. local TRANSLATION_ENABLED = BRIVEN_SECRET_KEY ~= "" and BRIVEN_PUBLISHABLE_KEY ~= "" and SERVICE_ROLE_KEY_ASYMMETRIC ~= "" and ANON_KEY_ASYMMETRIC ~= ""
  793. local PROTECTED_ROUTES = {
  794. ["auth-v1-protected"] = true,
  795. ["rest-v1-protected"] = true,
  796. ["graphql-v1-protected"] = true,
  797. ["realtime-v1-api-protected"] = true,
  798. ["realtime-v1-ws-protected"] = true,
  799. ["pg-protected"] = true,
  800. }
  801. local function is_protected_route(route_name)
  802. if route_name == nil or route_name == "" then
  803. return false
  804. end
  805. return PROTECTED_ROUTES[route_name] == true
  806. end
  807. local function is_valid_apikey(apikey)
  808. if apikey == nil or apikey == "" then
  809. return false
  810. end
  811. if SERVICE_ROLE_KEY ~= "" and apikey == SERVICE_ROLE_KEY then
  812. return true
  813. end
  814. if ANON_KEY ~= "" and apikey == ANON_KEY then
  815. return true
  816. end
  817. if TRANSLATION_ENABLED and apikey == SERVICE_ROLE_KEY_ASYMMETRIC then
  818. return true
  819. end
  820. if TRANSLATION_ENABLED and apikey == ANON_KEY_ASYMMETRIC then
  821. return true
  822. end
  823. return false
  824. end
  825. function envoy_on_request(request_handle)
  826. local headers = request_handle:headers()
  827. local route_name = request_handle:streamInfo():routeName()
  828. if not is_protected_route(route_name) then
  829. return
  830. end
  831. if is_valid_apikey(headers:get("apikey")) then
  832. return
  833. end
  834. request_handle:respond({
  835. [":status"] = "401",
  836. ["content-type"] = "text/plain",
  837. }, "Unauthorized")
  838. end
  839. - name: envoy.filters.http.rbac
  840. typed_config:
  841. '@type': >-
  842. type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC
  843. rules:
  844. action: ALLOW
  845. policies:
  846. admin:
  847. permissions:
  848. - url_path:
  849. path:
  850. prefix: /pg/
  851. principals:
  852. - header:
  853. name: apikey
  854. string_match:
  855. exact: '${SERVICE_ROLE_KEY}'
  856. - header:
  857. name: apikey
  858. string_match:
  859. exact: '${SERVICE_ROLE_KEY_ASYMMETRIC}'
  860. apikey:
  861. permissions:
  862. - url_path:
  863. path:
  864. prefix: /auth/v1/
  865. - url_path:
  866. path:
  867. prefix: /rest/v1/
  868. - url_path:
  869. path:
  870. prefix: /realtime/v1/api
  871. - url_path:
  872. path:
  873. prefix: /realtime/v1/
  874. - url_path:
  875. path:
  876. prefix: /graphql/v1
  877. principals:
  878. - header:
  879. name: apikey
  880. string_match:
  881. exact: '${SERVICE_ROLE_KEY}'
  882. - header:
  883. name: apikey
  884. string_match:
  885. exact: '${ANON_KEY}'
  886. - header:
  887. name: apikey
  888. string_match:
  889. exact: '${SERVICE_ROLE_KEY_ASYMMETRIC}'
  890. - header:
  891. name: apikey
  892. string_match:
  893. exact: '${ANON_KEY_ASYMMETRIC}'
  894. - name: envoy.filters.http.router
  895. typed_config:
  896. '@type': >-
  897. type.googleapis.com/envoy.extensions.filters.http.router.v3.Router