-
Notifications
You must be signed in to change notification settings - Fork 62
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
base: master
Are you sure you want to change the base?
Changes from all commits
5a5ff5e
5754568
1931ef2
6ef3eb4
413aca4
3490d39
8b5e172
7809674
cc4a5ba
8ceb8b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
||
|
@@ -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). | ||
|
||
## 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
``` | ||
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -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). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
``` | ||
|
@@ -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 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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()); | ||
|
@@ -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") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is really just stylistic, but why not all in one:
|
||
|
||
use std::os::raw::{c_char, c_void}; | ||
|
||
struct Module; | ||
|
@@ -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()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
itan opinionated SDK