Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
with:
version: "25"
- name: Check C++ Format
run: /opt/wasi-sdk/bin/clang-format --dry-run --Werror src/**/*.h src/**/*.cpp
run: /opt/wasi-sdk/bin/clang-format --dry-run --Werror include/**/*.h src/**/*.h src/**/*.cpp

test:
runs-on: ubuntu-latest
Expand Down
49 changes: 20 additions & 29 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ edition = "2024"

[dependencies]
cxx = { version = "1.0.158", features = ["c++17"] }
fastly = "0.11.4"
fastly-shared = "0.11.5"
fastly = "0.11.9"
fastly-shared = "0.11.9"
http = "1.3.1"
log = "0.4.27"
log-fastly = "0.11.5"
log-fastly = "0.11.9"
thiserror = "2.0.12"
esi = "0.6.1"
quick-xml = "0.38.3"
Expand Down
26 changes: 26 additions & 0 deletions examples/ngwaf_inspect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//! @example ngwaf_inspect.cpp
#include "fastly/sdk.h"

int main() {
fastly::log::init_simple("logs");
auto req{fastly::Request::from_client()};

auto ires{fastly::security::inspect(
req,
fastly::security::InspectConfig().with_corp("my_corp").with_workspace(
"my_workspace"))};
auto verdict{ires->verdict()};

fastly::Body body{"NGWAF Verdict: "};
if (verdict == fastly::security::InspectVerdict::Allow) {
body << "Allow";
} else if (verdict == fastly::security::InspectVerdict::Block) {
body << "Block";
} else if (verdict == fastly::security::InspectVerdict::Unauthorized) {
body << "Unauthorized";
} else if (verdict == fastly::security::InspectVerdict::Other) {
body << *ires->unrecognized_verdict_info() << " (Other)";
}

fastly::Response::from_body(std::move(body)).send_to_client();
}
2 changes: 1 addition & 1 deletion include/fastly/backend.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#ifndef FASTLY_BACKEND_H
#define FASTLY_BACKEND_H

#include <chrono>
#include <fastly/error.h>
#include <fastly/http/request.h>
#include <fastly/sdk-sys.h>
#include <chrono>
#include <string>
#include <string_view>

Expand Down
4 changes: 2 additions & 2 deletions include/fastly/http/status_code.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#ifndef FASTLY_HTTP_STATUS_CODE_H
#define FASTLY_HTTP_STATUS_CODE_H

#include <cstdint>
#include <cstdlib>
#include <fastly/error.h>
#include <fastly/expected.h>
#include <fastly/sdk-sys.h>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <optional>

Expand Down
110 changes: 110 additions & 0 deletions include/fastly/security.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#ifndef FASTLY_SECURITY_H
#define FASTLY_SECURITY_H

#include <fastly/detail/access_bridge_internals.h>
#include <fastly/expected.h>
#include <fastly/http/body.h>
#include <fastly/http/request.h>
#include <optional>

namespace fastly::security {
/// Configuration for inspecting a `Request` using Security.
class InspectConfig {
public:
/// Create a new default `InspectConfig`
InspectConfig() = default;

/// Specify an explicity client IP address to inspect.
/// By default, inspect will use the IP address that made the request to the
/// running Compute service, but you may want to use a different IP when
/// service chaining or if requests are proxied from outside of Fastly’s
/// network.
InspectConfig with_client_ip(std::string ip) && {
this->client_ip_ = std::move(ip);
return std::move(*this);
}

/// Set a corp name for the configuration.
InspectConfig with_corp(std::string name) && {
this->corp_ = std::move(name);
return std::move(*this);
}

/// Set a workspace name for the configuration.
InspectConfig with_workspace(std::string name) && {
this->workspace_ = std::move(name);
return std::move(*this);
}

/// Set a buffer size for the response.
InspectConfig with_buffer_size(std::size_t size) && {
this->buffer_size_ = size;
return std::move(*this);
}

const std::optional<std::string> &client_ip() const { return client_ip_; }
const std::optional<std::string> &corp() const { return corp_; }
const std::optional<std::string> &workspace() const { return workspace_; }
const std::optional<std::size_t> &buffer_size() const { return buffer_size_; }

private:
std::optional<std::string> client_ip_;
std::optional<std::string> corp_;
std::optional<std::string> workspace_;
std::optional<std::size_t> buffer_size_;
};
using fastly::sys::security::InspectErrorCode;
using fastly::sys::security::InspectVerdict;
class InspectError {
public:
InspectError(fastly::sys::security::InspectError *e)
: err_(rust::Box<fastly::sys::security::InspectError>::from_raw(e)) {};
InspectError(rust::Box<fastly::sys::security::InspectError> e)
: err_(std::move(e)) {};
InspectErrorCode error_code();
std::string error_msg();
/// When getting `InspectErrorCode::BufferSizeError`, this can be used to get
/// the required size of the buffer before re-attempting the call.
std::optional<std::size_t> required_buffer_size();

private:
rust::Box<fastly::sys::security::InspectError> err_;
};

/// Results of asking Security to inspect a `Request`
class InspectResponse {
friend detail::AccessBridgeInternals;

public:
/// Security status code.
std::int16_t status() const;
/// A redirect URL returned from Security
std::optional<std::string> redirect_url() const;
/// Tags returned by Security
std::vector<std::string> tags() const;
/// Get Security's verdict on how to handle this request.
InspectVerdict verdict() const;
/// Get additional information for verdicts where `this->verdict()` is
/// `Other`.
std::optional<std::string> unrecognized_verdict_info() const;
/// How long Security spent determining its verdict.
std::chrono::milliseconds decision_ms() const;
/// A redirect URI returned by Security.
bool is_redirect() const;
/// Convert a redirect URI returned by Security into a `Response`.
std::optional<Response> into_redirect();

private:
rust::Box<fastly::sys::security::InspectResponse> ir_;
InspectResponse(rust::Box<fastly::sys::security::InspectResponse> ir)
: ir_(std::move(ir)) {};
};

/// Inspect a `Request` using the [Fastly Next-Gen
/// WAF](https://docs.fastly.com/en/ngwaf/).
tl::expected<InspectResponse, InspectError>
Comment thread
zkat marked this conversation as resolved.
inspect(fastly::http::Request &request, InspectConfig config);

} // namespace fastly::security

#endif
2 changes: 1 addition & 1 deletion src/cpp/http/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
namespace fastly::http {
using fastly::sys::http::Method;
using fastly::sys::http::Version;
}
} // namespace fastly::http

#endif
28 changes: 11 additions & 17 deletions src/cpp/http/response.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ fastly::expected<Response> Response::with_header(std::string_view name,
});
}

fastly::expected<Response> Response::with_set_header(std::string_view name,
std::string_view value) && {
fastly::expected<Response>
Response::with_set_header(std::string_view name, std::string_view value) && {
return this->set_header(name, value).map([this]() {
return std::move(*this);
});
Expand All @@ -180,12 +180,13 @@ Response::get_header(std::string_view name) {
std::vector<uint8_t> value;
bool is_sensitive{false};
fastly::sys::error::FastlyError *err;
bool has_header{
this->res->get_header(static_cast<std::string>(name), value, is_sensitive, err)};
bool has_header{this->res->get_header(static_cast<std::string>(name), value,
is_sensitive, err)};
if (err != nullptr) {
return fastly::unexpected(err);
} else if (has_header) {
return std::optional<HeaderValue>(std::in_place, std::string(value.begin(), value.end()), is_sensitive);
return std::optional<HeaderValue>(
std::in_place, std::string(value.begin(), value.end()), is_sensitive);
} else {
return std::nullopt;
}
Expand All @@ -204,16 +205,13 @@ Response::get_header_all(std::string_view name) {
}
}

fastly::expected<HeadersRange>
Response::get_headers() {
fastly::expected<HeadersRange> Response::get_headers() {
fastly::sys::http::HeadersIter *out;
this->res->get_headers(out);
return HeadersRange(
rust::Box<fastly::sys::http::HeadersIter>::from_raw(out));
return HeadersRange(rust::Box<fastly::sys::http::HeadersIter>::from_raw(out));
}

fastly::expected<HeaderNamesRange>
Response::get_header_names() {
fastly::expected<HeaderNamesRange> Response::get_header_names() {
fastly::sys::http::HeaderNamesIter *out;
this->res->get_header_names(out);
return HeaderNamesRange(
Expand Down Expand Up @@ -344,13 +342,9 @@ Response::get_stale_while_revalidate() {
}
}

Version Response::get_version() {
return this->res->get_version();
}
Version Response::get_version() { return this->res->get_version(); }

void Response::set_version(Version version) {
this->res->set_version(version);
}
void Response::set_version(Version version) { this->res->set_version(version); }

Response Response::with_version(Version version) && {
this->set_version(version);
Expand Down
Loading