Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: multiple changes #43

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
18 changes: 12 additions & 6 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = [

[package]
name = "ngx"
version = "0.4.0-beta"
version = "0.5.0"
edition = "2021"
autoexamples = false
categories = ["api-bindings", "network-programming"]
Expand All @@ -19,7 +19,7 @@ keywords = ["nginx", "module", "sys"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
nginx-sys = { path = "nginx-sys", version = "0.2"}
nginx-sys = { path = "nginx-sys", version = "0.3"}

[badges]
maintenance = { status = "experimental" }
1 change: 0 additions & 1 deletion GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ help:

.PHONY: clean
clean: clean-cache; $(info $(M) cleaning...) @ ## Cleanup everything
$Q rm -rf $(CURDIR)/target
$Q $(CARGO) clean

.PHONY: clean-cache
Expand Down
35 changes: 20 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


## Project status
This project is still a work in progress and not production ready.
This project is still a work in progress and not production-ready.

# Description

Expand All @@ -14,23 +14,29 @@ dynamic modules completely in Rust.

In short, this SDK allows writing NGINX modules using the Rust language.

It contains the following Rust crates:
* [nginx-sys](./nginx-sys) - allows to use ngx_* C functions via FFI when implementing modules. The `-sys` is used to follow a [naming convention](https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages) to link with a C library.
* [ngx](./src) - it an opinionated SDK to make it a bit easer to use [nginx-sys](./nginx-sys) crate. Implements `macro_rules`, provides a way to build a dynamic module without any C code (see `ngx_modules!` macro_rule).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it an opinionated SDK


## Build

NGINX modules can be built against a particular version of NGINX. The following environment variables can be used to specify a particular version of NGINX or an NGINX dependency:

* `ZLIB_VERSION` (default 1.2.13) -
* `ZLIB_VERSION` (default 1.3)
* `PCRE2_VERSION` (default 10.42)
* `OPENSSL_VERSION` (default 3.0.7)
* `NGX_VERSION` (default 1.23.3) - NGINX OSS version
* `NGX_DEBUG` (default to false)- if set to true, then will compile NGINX `--with-debug` option
* `NGX_SRC_DIR` (default not set) - When the value is set, then use this NGINX source code folder to build bindings from
* `NGX_CONFIGURE_ARGS` (default not set) - When the value is set, then run the NGINX configure script with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest: When the value is set, use these arguments with the NGINX configure script


For example, this is how you would compile the [examples](examples) using a specific version of NGINX and enabling
debugging:
```
NGX_DEBUG=true NGX_VERSION=1.23.0 cargo build --package=examples --examples --release
```

To build Linux-only modules, use the "linux" feature:
To build Linux-only modules, use the `linux` feature:
```
cargo build --package=examples --examples --features=linux --release
```
Expand All @@ -43,13 +49,13 @@ the SDK. You can start NGINX directly from this directory if you want to test th

### Mac OS dependencies

In order to use the optional GNU make build process on MacOS, you will need to install additional tools. This can be
To use the optional GNU build process on MacOS, you will need to install additional tools. This can be
done via [homebrew](https://brew.sh/) with the following command:
```
brew install make openssl grep
```

Additionally, you may need to set up LLVM and clang. Typically, this is done as follows:
Additionally, you may need to set up LLVM and Clang. Typically, this is done as follows:

```
# make sure xcode tools are installed
Expand All @@ -64,26 +70,25 @@ See the [Dockerfile](Dockerfile) for dependencies as an example of required pack

### Build example

Example modules are available in [examples](examples) folder. You can use `cargo build --package=examples --examples` to build these examples. After building, you can find the `.so` or `.dylib` in the `target/debug` folder. Add `--features=linux` to build linux specific modules. **NOTE**: adding the "linux" feature on MacOS will cause a build failure.
Example modules are available in [examples](examples) folder. You can use `cargo build --package=examples --examples` to build these examples. After building, you can find the `.so` or `.dylib` in the `target/debug` folder. Add `--features=linux` to build Linux-specific modules. **NOTE**: adding the `linux` feature on MacOS will cause a build failure.

For example (all examples plus linux specific):
For example (all examples plus Linux specific):
`cargo build --packages=examples --examples --features=linux`

### Docker

We provide a multistage [Dockerfile](Dockerfile):

# build all dynamic modules examples and specify NGINX version to use
# Build all dynamic module examples and specify the NGINX version to use
docker buildx build --build-arg NGX_VERSION=1.23.3 -t ngx-rust .

# start NGINX using [curl](examples/curl.conf) module example:
docker run --rm -d -p 8000:8000 ngx-rust nginx -c examples/curl.conf
docker run --name curl --rm -d -p 8000:8000 ngx-rust nginx -c examples/curl.conf

# test it - you should see 403 Forbidden
# Test it - you should see 403 Forbidden
curl http://127.0.0.1:8000 -v -H "user-agent: curl"


# test it - you should see 404 Not Found
# Test it - you should see 404 Not Found
curl http://127.0.0.1:8000 -v -H "user-agent: foo"

## Usage
Expand Down Expand Up @@ -124,15 +129,15 @@ http {
```

## Support
This SDK is currently unstable. Right now, our primary goal is collect feedback and stabilize it be before
offering support. Feel free [contributing](CONTRIBUTING.md) by creating issues, PRs, or github discussions.
This SDK is currently unstable. Right now, our primary goal is to collect feedback and stabilize it
before offering support. Feel free to [contribute](CONTRIBUTING.md) by creating issues, PRs, or GitHub discussions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know if we link to the community slack anywhere? That would be a convenient way for people to ask question.


Currently, the only supported platforms are:
* Darwin (Mac OSX)
* Linux platform

## Roadmap
If you have ideas for releases in the future, please suggest them in the github discussions.
If you have ideas for releases in the future, please suggest them in the GitHub discussions.

## Contributing

Expand Down
8 changes: 4 additions & 4 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@


# Examples
This crate provides a couple of example using [ngx](https://crates.io/crates/ngx) crate:
This crate provides a couple of examples using [ngx](https://crates.io/crates/ngx)](https://crates.io/crates/ngx) crate:

- [awssig.rs](./awssig.rs) - An example of NGINX dynamic module that can sign GET request using AWS Signature v4.
- [awssig.rs](./awssig.rs) - An example of an NGINX dynamic module that can sign GET requests using AWS Signature v4.
- [curl](./curl.rs) - An example of the Access Phase NGINX dynamic module that blocks HTTP requests if `user-agent` header starts with `curl`.
- [httporigdst](./httporigdst.rs) - A dynamic module recovers the original IP address and port number of the destination packet.
- [upstream](./upstream.rs) - A dynamic module demonstrating the setup code to write an upstream filter or load balancer.
Expand All @@ -29,7 +29,7 @@ cargo build --package=examples --examples

This module demonstrates how to create a minimal dynamic module with `http_request_handler`, that checks for User-Agent headers and returns status code 403 if UA starts with `curl`, if a module is disabled then uses `core::Status::NGX_DECLINED` to indicate the operation is rejected, for example, because it is disabled in the configuration (`curl off`). Additionally, it demonstrates how to write a defective parser.

An example of nginx configuration file that uses that module can be found at [curl.conf](./curl.conf).
An example of an Nginx configuration file that uses that module can be found at [curl.conf](./curl.conf).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't been checking these elsewhere, but I'm pretty sure the convention is to all-caps NGINX.

I'll confirm with Jodie.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed with Jodie, NGINX should be all capitalized according to our style guide.


How to build and run in a [Docker](../Dockerfile) container curl example:
```
Expand All @@ -51,7 +51,7 @@ curl http://127.0.0.1:8000 -v -H "user-agent: foo"

This module uses [NGX_HTTP_PRECONTENT_PHASE](https://nginx.org/en/docs/dev/development_guide.html#http_phases) and provides examples, of how to use external dependency and manipulate HTTP headers before sending client requests upstream.

An example of nginx configuration file that uses that module can be found at [awssig.conf](./awssig.conf).
An example of an Nginx configuration file that uses that module can be found at [awssig.conf](./awssig.conf).

## HTTPORIGDST - NGINX Destination IP recovery module for HTTP

Expand Down
23 changes: 14 additions & 9 deletions examples/awssig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,13 @@ http_request_handler!(awssigv4_header_handler, |request: &mut Request| {
// Iterate over requests headers_in and copy into HeaderMap
// Copy only headers that will be used to sign the request
let mut headers = HeaderMap::new();
for (name, value) in request.headers_in_iterator() {
match name.to_lowercase().as_str() {
"host" => {
headers.insert(http::header::HOST, value.parse().unwrap());
for h in request.headers_in_iterator() {
match h.lowercase_key() {
Some("host") => {
headers.insert(http::header::HOST, h.value().parse().unwrap());
}
&_ => {}
Some(&_) => {}
None => {}
};
}
headers.insert("X-Amz-Date", datetime_now.parse().unwrap());
Expand All @@ -334,11 +335,15 @@ http_request_handler!(awssigv4_header_handler, |request: &mut Request| {
request.add_header_in("X-Amz-Date", datetime_now.as_str());

// done signing, let's print values we have in request.headers_out, request.headers_in
for (name, value) in request.headers_out_iterator() {
ngx_log_debug_http!(request, "headers_out {}: {}", name, value);
for h in request.headers_out_iterator() {
ngx_log_debug_http!(request, "headers_out {}", h);
}
for (name, value) in request.headers_in_iterator() {
ngx_log_debug_http!(request, "headers_in {}: {}", name, value);
for mut h in request.headers_in_iterator() {
ngx_log_debug_http!(request, "headers_in {}", h);
if h.lowercase_key() == Some("foo") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this testing code? What's the 'foo' header in this context?

let new_val = "value + ".to_owned() + h.value();
h.set_value(&new_val);
}
}

core::Status::NGX_OK
Expand Down
4 changes: 2 additions & 2 deletions examples/curl.conf
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ http {
listen *:8000;
server_name localhost;
location / {
root html;
root /usr/share/nginx/html;
index index.html index.htm;
# libcurl module directive:
curl on;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
root /usr/share/nginx/html;
}
}
}
30 changes: 21 additions & 9 deletions examples/curl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use ngx::ffi::{
ngx_uint_t, NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_MODULE, NGX_RS_HTTP_LOC_CONF_OFFSET,
NGX_RS_MODULE_SIGNATURE,
};
use ngx::http::MergeConfigError;
use ngx::{core, core::Status, http, http::HTTPModule};
use ngx::{http_request_handler, ngx_log_debug_http, ngx_modules, ngx_null_command, ngx_string};
use ngx::{core, core::Status, http, http::HTTPModule, http::MergeConfigError};
use ngx::{http_request_handler, ngx_log_debug, ngx_modules, ngx_null_command, ngx_string};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really just stylistic, but why not all in one:

use ngx::{core, core::Status, http, http::{HTTPModule, MergeConfigError}, http_request_handler, ngx_log_debug, ngx_modules, ngx_null_command, ngx_string};


use std::os::raw::{c_char, c_void};

struct Module;
Expand Down Expand Up @@ -104,18 +104,30 @@ impl http::Merge for ModuleConfig {
}

http_request_handler!(curl_access_handler, |request: &mut http::Request| {
// we can check if a request is internal and disable handler
let log = request.log();
ngx_log_debug!(log, "is internal: {}", request.is_internal());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this is printed will there be enough context for a user to know what's internal? Is there a convention around module identification for context?


if request.is_internal() {
return core::Status::NGX_DECLINED;
}
// get location config
let co = unsafe { request.get_module_loc_conf::<ModuleConfig>(&ngx_http_curl_module) };
let co = co.expect("module config is none");

ngx_log_debug_http!(request, "curl module enabled: {}", co.enable);

// check if module is enabled based on the location config
ngx_log_debug!(log, "curl module enabled: {}", co.enable);
if request.is_internal() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll have already bailed out at line 111.

return core::Status::NGX_DECLINED;
}
match co.enable {
true => {
if request.user_agent().as_bytes().starts_with(b"curl") {
http::HTTPStatus::FORBIDDEN.into()
} else {
core::Status::NGX_DECLINED
if let Some(ua) = request.user_agent() {
if ua.as_bytes().starts_with(b"curl") {
return http::HTTPStatus::FORBIDDEN.into();
}
}
return core::Status::NGX_DECLINED;
}
false => core::Status::NGX_DECLINED,
}
Expand Down
4 changes: 2 additions & 2 deletions nginx-sys/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "nginx-sys"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
categories = ["external-ffi-bindings"]
description = "FFI bindings to NGINX"
Expand All @@ -15,7 +15,7 @@ crate-type = ["staticlib", "rlib"]
[dependencies]

[build-dependencies]
bindgen = "0.65.0"
bindgen = "0.66.0"
which = "4.4.0"
duct = "0.13.6"
ureq = { version = "2.6.2", features = ["tls"] }
Expand Down
Loading
Loading