Skip to content

Commit 50fe366

Browse files
committed
feat(inspect): add support for NGWAF inspect api
1 parent abaab6a commit 50fe366

File tree

8 files changed

+283
-87
lines changed

8 files changed

+283
-87
lines changed

runtime/fastly/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
cmake_minimum_required(VERSION 3.27)
22

3+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
4+
35
include("../StarlingMonkey/cmake/add_as_subproject.cmake")
46

57
add_builtin(

runtime/fastly/builtins/fastly.cpp

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,116 @@ bool Fastly::getGeolocationForIpAddress(JSContext *cx, unsigned argc, JS::Value
169169
return JS_ParseJSON(cx, geo_info_str, args.rval());
170170
}
171171

172+
bool Fastly::inspect(JSContext *cx, unsigned argc, JS::Value *vp) {
173+
JS::CallArgs args = CallArgsFromVp(argc, vp);
174+
REQUEST_HANDLER_ONLY("inspect");
175+
if (!args.requireAtLeast(cx, "inspect", 1)) {
176+
return false;
177+
}
178+
179+
auto request_value = args.get(0);
180+
if (!Request::is_instance(request_value)) {
181+
JS_ReportErrorUTF8(cx, "inspect: request parameter must be an instance of Request");
182+
return false;
183+
}
184+
auto inspect_response_obj = &request_value.toObject();
185+
186+
auto options_value = args.get(1);
187+
JS::RootedObject options_obj(cx, options_value.isObject() ? &options_value.toObject() : nullptr);
188+
189+
host_api::InspectOptions inspect_options(Request::request_handle(inspect_response_obj),
190+
RequestOrResponse::body_handle(inspect_response_obj));
191+
192+
if (options_value != nullptr) {
193+
194+
host_api::HostString corp_str;
195+
JS::RootedValue corp_val(cx);
196+
if (!JS_GetProperty(cx, options, "corp", &corp_val)) {
197+
return false;
198+
}
199+
if (!corp_val.isNullOrUndefined()) {
200+
if (!corp_val.isString()) {
201+
api::throw_error(cx, api::Errors::TypeError, "inspect", "corp", "be a string");
202+
return false;
203+
}
204+
corp_str = core::encode(cx, corp_val);
205+
if (!corp_str) {
206+
return false;
207+
}
208+
std::optional<std::string_view> corp = corp_str;
209+
if (corp) {
210+
inspect_options.corp_len = corp->length();
211+
inspect_options.corp = std::move(corp->data());
212+
}
213+
}
214+
215+
host_api::HostString workspace_str;
216+
JS::RootedValue workspace_val(cx);
217+
if (!JS_GetProperty(cx, options, "workspace", &workspace_val)) {
218+
return false;
219+
}
220+
if (!workspace_val.isNullOrUndefined()) {
221+
if (!workspace_val.isString()) {
222+
api::throw_error(cx, api::Errors::TypeError, "inspect", "workspace", "be a string");
223+
return false;
224+
}
225+
workspace_str = core::encode(cx, workspace_val);
226+
if (!workspace_str) {
227+
return false;
228+
}
229+
std::optional<std::string_view> workspace = workspace_str;
230+
if (workspace) {
231+
inspect_options.workspace_len = workspace->length();
232+
inspect_options.workspace = std::move(workspace->data());
233+
}
234+
}
235+
236+
host_api::HostString override_client_ip_str;
237+
JS::RootedValue override_client_ip_val(cx);
238+
if (!JS_GetProperty(cx, options, "overrideClientIp", &override_client_ip_val)) {
239+
return false;
240+
}
241+
if (!override_client_ip_val.isNullOrUndefined()) {
242+
if (!override_client_ip_val.isString()) {
243+
api::throw_error(cx, api::Errors::TypeError, "fastly.inspect", "overrideClientIp",
244+
"be a string");
245+
return false;
246+
}
247+
override_client_ip_str = core::encode(cx, override_client_ip_val);
248+
if (!override_client_ip_str) {
249+
return false;
250+
}
251+
252+
// TODO: Remove all of this and rely on the host for validation as the hostcall only takes one
253+
// user-supplied parameter
254+
int format = AF_INET;
255+
size_t octets_len = 4;
256+
if (std::find(override_client_ip_str.begin(), override_client_ip_str.end(), ':') !=
257+
override_client_ip_str.end()) {
258+
format = AF_INET6;
259+
octets_len = 16;
260+
}
261+
262+
uint8_t octets[sizeof(struct in6_addr)];
263+
if (inet_pton(format, override_client_ip_str.begin(), octets) != 1) {
264+
api::throw_error(cx, api::Errors::TypeError, "fastly.inspect", "overrideClientIp",
265+
"be a valid IP address");
266+
return false;
267+
}
268+
inspect_options.override_client_ip_len = octets_len;
269+
inspect_options.override_client_ip = std::move(octets->data());
270+
}
271+
}
272+
273+
auto res = request_value->inspect(&inspect_options);
274+
if (auto *err = res.to_err()) {
275+
HANDLE_ERROR(cx, *err);
276+
return false;
277+
}
278+
279+
return JS_ParseJSON(cx, inspect_info_str, args.rval());
280+
}
281+
172282
// TODO(performance): consider allowing logger creation during initialization, but then throw
173283
// when trying to log.
174284
// https://github.com/fastly/js-compute-runtime/issues/225
@@ -610,6 +720,7 @@ bool install(api::Engine *engine) {
610720
JS_FN("enableDebugLogging", Fastly::enableDebugLogging, 1, JSPROP_ENUMERATE),
611721
JS_FN("debugLog", debugLog, 1, JSPROP_ENUMERATE),
612722
JS_FN("getGeolocationForIpAddress", Fastly::getGeolocationForIpAddress, 1, JSPROP_ENUMERATE),
723+
JS_FN("inspect", Fastly::inspect, 1, JSPROP_ENUMERATE),
613724
JS_FN("getLogger", Fastly::getLogger, 1, JSPROP_ENUMERATE),
614725
JS_FN("includeBytes", Fastly::includeBytes, 1, JSPROP_ENUMERATE),
615726
JS_FN("createFanoutHandoff", Fastly::createFanoutHandoff, 2, JSPROP_ENUMERATE),
@@ -751,6 +862,21 @@ bool install(api::Engine *engine) {
751862
if (!engine->define_builtin_module("fastly:fanout", fanout_val)) {
752863
return false;
753864
}
865+
866+
// fastly:security
867+
RootedValue inspect_val(engine->cx());
868+
if (!JS_GetProperty(engine->cx(), fastly, "inspect", &inspect_val)) {
869+
return false;
870+
}
871+
RootedObject security_builtin(engine->cx(), JS_NewObject(engine->cx(), nullptr));
872+
RootedValue security_builtin_val(engine->cx(), JS::ObjectValue(*security_builtin));
873+
if (!JS_SetProperty(engine->cx(), security_builtin, "inspect", inspect_val)) {
874+
return false;
875+
}
876+
if (!engine->define_builtin_module("fastly:security", security_builtin_val)) {
877+
return false;
878+
}
879+
754880
// fastly:websocket
755881
RootedObject websocket(engine->cx(), JS_NewObject(engine->cx(), nullptr));
756882
RootedValue websocket_val(engine->cx(), JS::ObjectValue(*websocket));

runtime/fastly/builtins/fastly.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class Fastly : public builtins::BuiltinNoConstructor<Fastly> {
6060
static bool defaultBackend_set(JSContext *cx, unsigned argc, JS::Value *vp);
6161
static bool allowDynamicBackends_get(JSContext *cx, unsigned argc, JS::Value *vp);
6262
static bool allowDynamicBackends_set(JSContext *cx, unsigned argc, JS::Value *vp);
63+
static bool inspect(JSContext *cx, unsigned argc, JS::Value *vp);
6364
};
6465

6566
JS::Result<std::tuple<JS::UniqueChars, size_t>> convertBodyInit(JSContext *cx,

runtime/fastly/builtins/fetch/request-response.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ class Response final : public builtins::FinalizableBuiltinImpl<Response> {
289289
/**
290290
* Base-level response creation handler, for both upstream and downstream requests.
291291
*/
292-
static JSObject *create(JSContext *cx, JS::HandleObject response,
292+
staticeJSObject *create(JSContext *cx, JS::HandleObject response,
293293
host_api::HttpResp response_handle, host_api::HttpBody body_handle,
294294
bool is_upstream, JSObject *grip_upgrade_request,
295295
JSObject *websocket_upgrade_request, JS::HandleString backend);

runtime/fastly/host-api/fastly.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,15 @@ typedef struct fastly_host_http_response {
3939
uint32_t f1;
4040
} fastly_host_http_response;
4141

42+
typedef struct fastly_host_http_inspect_options {
43+
uint8_t *corp;
44+
uint32_t corp_len;
45+
uint8_t *workspace;
46+
uint32_t workspace_len;
47+
uint8_t *override_client_ip_ptr;
48+
uint32_t override_client_ip_len;
49+
} fastly_host_http_inspect_options;
50+
4251
typedef fastly_host_http_response fastly_world_tuple2_handle_handle;
4352

4453
#define WASM_IMPORT(module, name) __attribute__((import_module(module), import_name(name)))
@@ -265,6 +274,13 @@ typedef enum BodyWriteEnd {
265274
#define CACHE_OVERRIDE_STALE_WHILE_REVALIDATE (1u << 2)
266275
#define CACHE_OVERRIDE_PCI (1u << 3)
267276

277+
typedef uint32_t req_inspect_config_options_mask;
278+
279+
#define FASTLY_HOST_HTTP_REQ_INSPECT_CONFIG_OPTIONS_MASK_RESERVED = 1 << 0;
280+
#define FASTLY_HOST_HTTP_REQ_INSPECT_CONFIG_OPTIONS_MASK_CORP = 1 << 1;
281+
#define FASTLY_HOST_HTTP_REQ_INSPECT_CONFIG_OPTIONS_MASK_WORKSPACE = 1 << 2;
282+
#define FASTLY_HOST_HTTP_REQ_INSPECT_CONFIG_OPTIONS_MASK_OVERRIDE_CLIENT_IP = 1 << 3;
283+
268284
WASM_IMPORT("fastly_abi", "init")
269285
int init(uint64_t abi_version);
270286

@@ -620,6 +636,12 @@ int req_pending_req_wait_v2(uint32_t req_handle,
620636
fastly_host_http_send_error_detail *send_error_detail,
621637
uint32_t *resp_handle_out, uint32_t *resp_body_handle_out);
622638

639+
WASM_IMPORT("fastly_http_req", "inspect")
640+
int req_inspect(uint32_t req_handle, uint32_t body_handle,
641+
req_inspect_config_options_mask config_options_mask,
642+
fastly_host_http_inspect_options *config, uint8_t *inspect_res_buf,
643+
uint32_t inspect_res_buf_len, uint32_t *nwritten_out);
644+
623645
// Module fastly_http_resp
624646
WASM_IMPORT("fastly_http_resp", "new")
625647
int resp_new(uint32_t *resp_handle_out);

runtime/fastly/host-api/host_api.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,6 +2117,35 @@ Result<HostString> HttpReq::get_suggested_cache_key() const {
21172117
return Result<HostString>::ok(make_host_string(str));
21182118
}
21192119

2120+
Result<HostString> Request::inspect(const InspectConfig *config) {
2121+
TRACE_CALL()
2122+
uint32_t inspect_opts_mask{0};
2123+
2124+
if (config.corp != nullptr) {
2125+
inspect_opts_mask |= FASTLY_HOST_HTTP_REQ_INSPECT_CONFIG_OPTIONS_MASK_CORP;
2126+
}
2127+
2128+
if (config.workspace != nullptr) {
2129+
inspect_opts_mask |= FASTLY_HOST_HTTP_REQ_INSPECT_CONFIG_OPTIONS_MASK_WORKSPACE;
2130+
}
2131+
2132+
if (config.override_client_ip != nullptr) {
2133+
inspect_opts_mask |= FASTLY_HOST_HTTP_REQ_INSPECT_CONFIG_OPTIONS_MASK_OVERRIDE_CLIENT_IP;
2134+
}
2135+
2136+
fastly::fastly_host_error err;
2137+
fastly::fastly_world_string ret;
2138+
ret.ptr = static_cast<uint8_t *>(cabi_malloc(HOSTCALL_BUFFER_LEN, 4));
2139+
if (!convert_result(fastly::req_inspect(this->req.handle, this->body.handle, inspect_opts_mask,
2140+
config, ret.ptr, &ret.len, ),
2141+
&err)) {
2142+
res.emplace_err(err);
2143+
} else {
2144+
res.emplace(make_host_string(ret));
2145+
}
2146+
return res;
2147+
}
2148+
21202149
// HttpCacheEntry method implementations
21212150
Result<HttpCacheEntry> HttpCacheEntry::lookup(const HttpReq &req, std::span<uint8_t> override_key) {
21222151
TRACE_CALL()

runtime/fastly/host-api/host_api_fastly.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ struct TlsVersion {
374374
uint8_t value = 0;
375375

376376
explicit TlsVersion(uint8_t raw);
377-
explicit TlsVersion(){};
377+
explicit TlsVersion() {};
378378

379379
uint8_t get_version() const;
380380
double get_version_number() const;
@@ -515,6 +515,21 @@ enum class FramingHeadersMode : uint8_t {
515515
ManuallyFromHeaders,
516516
};
517517

518+
class InspectOptions final {
519+
public:
520+
uint8_t *corp = nullptr;
521+
uint32_t corp_len = 0;
522+
uint8_t *workspace = nullptr;
523+
uint32_t workspace_len = 0;
524+
uint8_t *override_client_ip_ptr = nullptr;
525+
uint32_t override_client_ip_len = 0;
526+
uint32_t req_handle;
527+
uint32_t body_handle;
528+
529+
InspectOptions() = default;
530+
explicit InspectOptions(uint32_t req, uint32_t body) : req_handle{req}, body_handle{body} {}
531+
};
532+
518533
class HttpReq final : public HttpBase {
519534
public:
520535
using Handle = uint32_t;
@@ -658,6 +673,8 @@ struct Request {
658673

659674
Request() = default;
660675
Request(HttpReq req, HttpBody body) : req{req}, body{body} {}
676+
677+
Result<HostString> inspect(const InspectConfig *config);
661678
};
662679

663680
class GeoIp final {

0 commit comments

Comments
 (0)