Skip to content

Commit 96d3af7

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

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

runtime/fastly/builtins/fastly.cpp

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,112 @@ 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 grip_upgrade_request = &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_value.req.handle, request_value.body.handle);
190+
191+
if (options_value != nullptr) {
192+
193+
host_api::HostString corp_str;
194+
JS::RootedValue corp_val(cx);
195+
if (!JS_GetProperty(cx, options, "corp", &corp_val)) {
196+
return false;
197+
}
198+
if (!corp_val.isNullOrUndefined()) {
199+
if (!corp_val.isString()) {
200+
api::throw_error(cx, api::Errors::TypeError, "inspect", "corp", "be a string");
201+
return false;
202+
}
203+
corp_str = core::encode(cx, corp_val);
204+
if (!corp_str) {
205+
return false;
206+
}
207+
std::optional<std::string_view> corp = corp_str;
208+
if (corp) {
209+
inspect_options.corp_len = corp->length();
210+
inspect_options.corp = std::move(corp->data());
211+
}
212+
}
213+
214+
host_api::HostString workspace_str;
215+
JS::RootedValue workspace_val(cx);
216+
if (!JS_GetProperty(cx, options, "workspace", &workspace_val)) {
217+
return false;
218+
}
219+
if (!workspace_val.isNullOrUndefined()) {
220+
if (!workspace_val.isString()) {
221+
api::throw_error(cx, api::Errors::TypeError, "inspect", "workspace", "be a string");
222+
return false;
223+
}
224+
workspace_str = core::encode(cx, workspace_val);
225+
if (!workspace_str) {
226+
return false;
227+
}
228+
std::optional<std::string_view> workspace = workspace_str;
229+
if (workspace) {
230+
inspect_options.workspace_len = workspace->length();
231+
inspect_options.workspace = std::move(workspace->data());
232+
}
233+
}
234+
235+
host_api::HostString override_client_ip_str;
236+
JS::RootedValue override_client_ip_val(cx);
237+
if (!JS_GetProperty(cx, options, "overrideClientIp", &override_client_ip_val)) {
238+
return false;
239+
}
240+
if (!override_client_ip_val.isNullOrUndefined()) {
241+
if (!override_client_ip_val.isString()) {
242+
api::throw_error(cx, api::Errors::TypeError, "fastly.inspect", "overrideClientIp", "be a string");
243+
return false;
244+
}
245+
override_client_ip_str = core::encode(cx, override_client_ip_val);
246+
if (!override_client_ip_str) {
247+
return false;
248+
}
249+
250+
// TODO: Remove all of this and rely on the host for validation as the hostcall only takes one
251+
// user-supplied parameter
252+
int format = AF_INET;
253+
size_t octets_len = 4;
254+
if (std::find(override_client_ip_str.begin(), override_client_ip_str.end(), ':') != override_client_ip_str.end()) {
255+
format = AF_INET6;
256+
octets_len = 16;
257+
}
258+
259+
uint8_t octets[sizeof(struct in6_addr)];
260+
if (inet_pton(format, override_client_ip_str.begin(), octets) != 1) {
261+
api::throw_error(cx, api::Errors::TypeError, "fastly.inspect", "overrideClientIp", "be a valid IP address");
262+
return false;
263+
}
264+
inspect_options.override_client_ip_len = octets_len;
265+
inspect_options.override_client_ip = std::move(octets->data());
266+
}
267+
}
268+
269+
auto res = request_value->inspect(&inspect_options);
270+
if (auto *err = res.to_err()) {
271+
HANDLE_ERROR(cx, *err);
272+
return false;
273+
}
274+
275+
return JS_ParseJSON(cx, inspect_info_str, args.rval());
276+
}
277+
172278
// TODO(performance): consider allowing logger creation during initialization, but then throw
173279
// when trying to log.
174280
// https://github.com/fastly/js-compute-runtime/issues/225
@@ -610,6 +716,7 @@ bool install(api::Engine *engine) {
610716
JS_FN("enableDebugLogging", Fastly::enableDebugLogging, 1, JSPROP_ENUMERATE),
611717
JS_FN("debugLog", debugLog, 1, JSPROP_ENUMERATE),
612718
JS_FN("getGeolocationForIpAddress", Fastly::getGeolocationForIpAddress, 1, JSPROP_ENUMERATE),
719+
JS_FN("inspect", Fastly::inspect, 1, JSPROP_ENUMERATE),
613720
JS_FN("getLogger", Fastly::getLogger, 1, JSPROP_ENUMERATE),
614721
JS_FN("includeBytes", Fastly::includeBytes, 1, JSPROP_ENUMERATE),
615722
JS_FN("createFanoutHandoff", Fastly::createFanoutHandoff, 2, JSPROP_ENUMERATE),
@@ -751,6 +858,23 @@ bool install(api::Engine *engine) {
751858
if (!engine->define_builtin_module("fastly:fanout", fanout_val)) {
752859
return false;
753860
}
861+
862+
// fastly:security
863+
RootedValue inspect_val(engine->cx());
864+
if (!JS_GetProperty(engine->cx(), fastly, "inspect",
865+
&inspect_val)) {
866+
return false;
867+
}
868+
RootedObject security_builtin(engine->cx(), JS_NewObject(engine->cx(), nullptr));
869+
RootedValue security_builtin_val(engine->cx(), JS::ObjectValue(*security_builtin));
870+
if (!JS_SetProperty(engine->cx(), security_builtin, "inspect",
871+
inspect_val)) {
872+
return false;
873+
}
874+
if (!engine->define_builtin_module("fastly:security", security_builtin_val)) {
875+
return false;
876+
}
877+
754878
// fastly:websocket
755879
RootedObject websocket(engine->cx(), JS_NewObject(engine->cx(), nullptr));
756880
RootedValue websocket_val(engine->cx(), JS::ObjectValue(*websocket));

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 overrice_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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,20 @@ enum class FramingHeadersMode : uint8_t {
515515
ManuallyFromHeaders,
516516
};
517517

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

659673
Request() = default;
660674
Request(HttpReq req, HttpBody body) : req{req}, body{body} {}
675+
676+
Result<HostString> inspect(const InspectConfig *config);
661677
};
662678

663679
class GeoIp final {

0 commit comments

Comments
 (0)