Skip to content

Commit ac25a06

Browse files
zkatTartanLlama
andauthored
feat(inspect): add support for NGWAF inspect api (#75)
Co-authored-by: Sy Brand <[email protected]>
1 parent f606f87 commit ac25a06

File tree

13 files changed

+527
-54
lines changed

13 files changed

+527
-54
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
with:
4949
version: "25"
5050
- name: Check C++ Format
51-
run: /opt/wasi-sdk/bin/clang-format --dry-run --Werror src/**/*.h src/**/*.cpp
51+
run: /opt/wasi-sdk/bin/clang-format --dry-run --Werror include/**/*.h src/**/*.h src/**/*.cpp
5252

5353
test:
5454
runs-on: ubuntu-latest

Cargo.lock

Lines changed: 20 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ edition = "2024"
66

77
[dependencies]
88
cxx = { version = "1.0.158", features = ["c++17"] }
9-
fastly = "0.11.4"
10-
fastly-shared = "0.11.5"
9+
fastly = "0.11.9"
10+
fastly-shared = "0.11.9"
1111
http = "1.3.1"
1212
log = "0.4.27"
13-
log-fastly = "0.11.5"
13+
log-fastly = "0.11.9"
1414
thiserror = "2.0.12"
1515
esi = "0.6.1"
1616
quick-xml = "0.38.3"

examples/ngwaf_inspect.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//! @example ngwaf_inspect.cpp
2+
#include "fastly/sdk.h"
3+
4+
int main() {
5+
fastly::log::init_simple("logs");
6+
auto req{fastly::Request::from_client()};
7+
8+
auto ires{fastly::security::inspect(
9+
req,
10+
fastly::security::InspectConfig().with_corp("my_corp").with_workspace(
11+
"my_workspace"))};
12+
auto verdict{ires->verdict()};
13+
14+
fastly::Body body{"NGWAF Verdict: "};
15+
if (verdict == fastly::security::InspectVerdict::Allow) {
16+
body << "Allow";
17+
} else if (verdict == fastly::security::InspectVerdict::Block) {
18+
body << "Block";
19+
} else if (verdict == fastly::security::InspectVerdict::Unauthorized) {
20+
body << "Unauthorized";
21+
} else if (verdict == fastly::security::InspectVerdict::Other) {
22+
body << *ires->unrecognized_verdict_info() << " (Other)";
23+
}
24+
25+
fastly::Response::from_body(std::move(body)).send_to_client();
26+
}

include/fastly/backend.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
#ifndef FASTLY_BACKEND_H
22
#define FASTLY_BACKEND_H
33

4+
#include <chrono>
45
#include <fastly/error.h>
56
#include <fastly/http/request.h>
67
#include <fastly/sdk-sys.h>
7-
#include <chrono>
88
#include <string>
99
#include <string_view>
1010

include/fastly/http/status_code.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#ifndef FASTLY_HTTP_STATUS_CODE_H
22
#define FASTLY_HTTP_STATUS_CODE_H
33

4+
#include <cstdint>
5+
#include <cstdlib>
46
#include <fastly/error.h>
57
#include <fastly/expected.h>
68
#include <fastly/sdk-sys.h>
7-
#include <cstdint>
8-
#include <cstdlib>
99
#include <iostream>
1010
#include <optional>
1111

include/fastly/security.h

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#ifndef FASTLY_SECURITY_H
2+
#define FASTLY_SECURITY_H
3+
4+
#include <fastly/detail/access_bridge_internals.h>
5+
#include <fastly/expected.h>
6+
#include <fastly/http/body.h>
7+
#include <fastly/http/request.h>
8+
#include <optional>
9+
10+
namespace fastly::security {
11+
/// Configuration for inspecting a `Request` using Security.
12+
class InspectConfig {
13+
public:
14+
/// Create a new default `InspectConfig`
15+
InspectConfig() = default;
16+
17+
/// Specify an explicity client IP address to inspect.
18+
/// By default, inspect will use the IP address that made the request to the
19+
/// running Compute service, but you may want to use a different IP when
20+
/// service chaining or if requests are proxied from outside of Fastly’s
21+
/// network.
22+
InspectConfig with_client_ip(std::string ip) && {
23+
this->client_ip_ = std::move(ip);
24+
return std::move(*this);
25+
}
26+
27+
/// Set a corp name for the configuration.
28+
InspectConfig with_corp(std::string name) && {
29+
this->corp_ = std::move(name);
30+
return std::move(*this);
31+
}
32+
33+
/// Set a workspace name for the configuration.
34+
InspectConfig with_workspace(std::string name) && {
35+
this->workspace_ = std::move(name);
36+
return std::move(*this);
37+
}
38+
39+
/// Set a buffer size for the response.
40+
InspectConfig with_buffer_size(std::size_t size) && {
41+
this->buffer_size_ = size;
42+
return std::move(*this);
43+
}
44+
45+
const std::optional<std::string> &client_ip() const { return client_ip_; }
46+
const std::optional<std::string> &corp() const { return corp_; }
47+
const std::optional<std::string> &workspace() const { return workspace_; }
48+
const std::optional<std::size_t> &buffer_size() const { return buffer_size_; }
49+
50+
private:
51+
std::optional<std::string> client_ip_;
52+
std::optional<std::string> corp_;
53+
std::optional<std::string> workspace_;
54+
std::optional<std::size_t> buffer_size_;
55+
};
56+
using fastly::sys::security::InspectErrorCode;
57+
using fastly::sys::security::InspectVerdict;
58+
class InspectError {
59+
public:
60+
InspectError(fastly::sys::security::InspectError *e)
61+
: err_(rust::Box<fastly::sys::security::InspectError>::from_raw(e)) {};
62+
InspectError(rust::Box<fastly::sys::security::InspectError> e)
63+
: err_(std::move(e)) {};
64+
InspectErrorCode error_code();
65+
std::string error_msg();
66+
/// When getting `InspectErrorCode::BufferSizeError`, this can be used to get
67+
/// the required size of the buffer before re-attempting the call.
68+
std::optional<std::size_t> required_buffer_size();
69+
70+
private:
71+
rust::Box<fastly::sys::security::InspectError> err_;
72+
};
73+
74+
/// Results of asking Security to inspect a `Request`
75+
class InspectResponse {
76+
friend detail::AccessBridgeInternals;
77+
78+
public:
79+
/// Security status code.
80+
std::int16_t status() const;
81+
/// A redirect URL returned from Security
82+
std::optional<std::string> redirect_url() const;
83+
/// Tags returned by Security
84+
std::vector<std::string> tags() const;
85+
/// Get Security's verdict on how to handle this request.
86+
InspectVerdict verdict() const;
87+
/// Get additional information for verdicts where `this->verdict()` is
88+
/// `Other`.
89+
std::optional<std::string> unrecognized_verdict_info() const;
90+
/// How long Security spent determining its verdict.
91+
std::chrono::milliseconds decision_ms() const;
92+
/// A redirect URI returned by Security.
93+
bool is_redirect() const;
94+
/// Convert a redirect URI returned by Security into a `Response`.
95+
std::optional<Response> into_redirect();
96+
97+
private:
98+
rust::Box<fastly::sys::security::InspectResponse> ir_;
99+
InspectResponse(rust::Box<fastly::sys::security::InspectResponse> ir)
100+
: ir_(std::move(ir)) {};
101+
};
102+
103+
/// Inspect a `Request` using the [Fastly Next-Gen
104+
/// WAF](https://docs.fastly.com/en/ngwaf/).
105+
tl::expected<InspectResponse, InspectError>
106+
inspect(fastly::http::Request &request, InspectConfig config);
107+
108+
} // namespace fastly::security
109+
110+
#endif

src/cpp/http/http.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
namespace fastly::http {
77
using fastly::sys::http::Method;
88
using fastly::sys::http::Version;
9-
}
9+
} // namespace fastly::http
1010

1111
#endif

src/cpp/http/response.cpp

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ fastly::expected<Response> Response::with_header(std::string_view name,
168168
});
169169
}
170170

171-
fastly::expected<Response> Response::with_set_header(std::string_view name,
172-
std::string_view value) && {
171+
fastly::expected<Response>
172+
Response::with_set_header(std::string_view name, std::string_view value) && {
173173
return this->set_header(name, value).map([this]() {
174174
return std::move(*this);
175175
});
@@ -180,12 +180,13 @@ Response::get_header(std::string_view name) {
180180
std::vector<uint8_t> value;
181181
bool is_sensitive{false};
182182
fastly::sys::error::FastlyError *err;
183-
bool has_header{
184-
this->res->get_header(static_cast<std::string>(name), value, is_sensitive, err)};
183+
bool has_header{this->res->get_header(static_cast<std::string>(name), value,
184+
is_sensitive, err)};
185185
if (err != nullptr) {
186186
return fastly::unexpected(err);
187187
} else if (has_header) {
188-
return std::optional<HeaderValue>(std::in_place, std::string(value.begin(), value.end()), is_sensitive);
188+
return std::optional<HeaderValue>(
189+
std::in_place, std::string(value.begin(), value.end()), is_sensitive);
189190
} else {
190191
return std::nullopt;
191192
}
@@ -204,16 +205,13 @@ Response::get_header_all(std::string_view name) {
204205
}
205206
}
206207

207-
fastly::expected<HeadersRange>
208-
Response::get_headers() {
208+
fastly::expected<HeadersRange> Response::get_headers() {
209209
fastly::sys::http::HeadersIter *out;
210210
this->res->get_headers(out);
211-
return HeadersRange(
212-
rust::Box<fastly::sys::http::HeadersIter>::from_raw(out));
211+
return HeadersRange(rust::Box<fastly::sys::http::HeadersIter>::from_raw(out));
213212
}
214213

215-
fastly::expected<HeaderNamesRange>
216-
Response::get_header_names() {
214+
fastly::expected<HeaderNamesRange> Response::get_header_names() {
217215
fastly::sys::http::HeaderNamesIter *out;
218216
this->res->get_header_names(out);
219217
return HeaderNamesRange(
@@ -344,13 +342,9 @@ Response::get_stale_while_revalidate() {
344342
}
345343
}
346344

347-
Version Response::get_version() {
348-
return this->res->get_version();
349-
}
345+
Version Response::get_version() { return this->res->get_version(); }
350346

351-
void Response::set_version(Version version) {
352-
this->res->set_version(version);
353-
}
347+
void Response::set_version(Version version) { this->res->set_version(version); }
354348

355349
Response Response::with_version(Version version) && {
356350
this->set_version(version);

0 commit comments

Comments
 (0)