Skip to content

Commit 4e4c821

Browse files
Merge pull request #22 from FireTail-io/enable-firetail-directive
Enable firetail directive
2 parents 55a0131 + 5d5abc9 commit 4e4c821

18 files changed

+581
-511
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,14 @@ To setup the Firetail NGINX Module, you will need to modify your `nginx.conf` to
2929
load_module modules/ngx_firetail_module.so;
3030
```
3131

32-
You can then configure it using the following directives, which all belong in the `http` block of your NGINX configuration:
33-
34-
| Directive | Description | Example |
35-
| --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
36-
| `firetail_api_token` | Your API token from the FireTail platform | `PS-02-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
37-
| `firetail_url` | The URL of the API endpoint the FireTail NGINX module will send logs to. | `https://api.logging.eu-west-1.prod.firetail.app/logs/bulk` |
38-
| `firetail_allow_undefined_routes` | If set to `1`, `t`, `T`, `TRUE`, `true`, or `True`, requests to routes not defined in your OpenAPI specification will not be blocked. | `1`, `t`, `T`, `TRUE`, `true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False` |
32+
You can then configure it using the following directives:
33+
34+
| Directive | Valid in | Description | Example Arguments |
35+
| --------------------------------- | ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
36+
| `firetail_api_token` | `http` | Your API token from the FireTail platform | `PS-02-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` |
37+
| `firetail_url` | `http` | The URL of the API endpoint the FireTail NGINX module will send logs to. | `https://api.logging.eu-west-1.prod.firetail.app/logs/bulk` |
38+
| `firetail_enable` | `location` | Use this in every location block for which you want FireTail to be enabled. | This directive takes no arguments. |
39+
| `firetail_allow_undefined_routes` | `http` | If set to `1`, `t`, `T`, `TRUE`, `true`, or `True`, requests to routes not defined in your OpenAPI specification will not be blocked. | `1`, `t`, `T`, `TRUE`, `true`, `True`, `0`, `f`, `F`, `FALSE`, `false`, `False` |
3940

4041
See [dev/nginx.conf](./dev/nginx.conf) for an example of these in use.
4142

dev/nginx.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,32 @@ http {
2424
}
2525

2626
location /health {
27+
firetail_enable;
2728
return 200 '{"message":"I\'m healthy! 💖"}';
2829
}
2930

3031
location /notfound {
32+
firetail_enable;
3133
return 404 '{"message":"Not Found 🤷"}';
3234
}
3335

3436
location /unhealthy {
37+
firetail_enable;
3538
return 500 '{"message":"I\'m unhealthy! 🤒"}';
3639
}
3740

3841
location /profile/alice {
42+
firetail_enable;
3943
return 200 '{"username":"alice", "friends": 123456789}';
4044
}
4145

4246
location /profile/bob {
47+
firetail_enable;
4348
return 200 '{"username":"bob", "friends": 123456789, "address":"Oh dear, this shouldn\'t be public!"}';
4449
}
4550

4651
location /profile/alice/comment {
52+
firetail_enable;
4753
return 201 '{"message":"Success!"}';
4854
}
4955

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
#include <ngx_core.h>
2+
#include <ngx_http.h>
3+
#include "access_phase_handler.h"
4+
#include "filter_context.h"
5+
#include "firetail_config.h"
6+
#include "firetail_module.h"
7+
#include <json-c/json.h>
8+
9+
static void FiretailClientBodyHandler(ngx_http_request_t *request);
10+
static ngx_int_t FiretailClientBodyHandlerInternal(ngx_http_request_t *request);
11+
static ngx_int_t FiretailReturnFailedValidationResult(ngx_http_request_t *request, ngx_buf_t *b,
12+
ngx_chain_t *chain_head, char *error);
13+
14+
struct ValidateRequestBody_return {
15+
int r0;
16+
char *r1;
17+
};
18+
typedef struct ValidateRequestBody_return (*ValidateRequestBody)(void *, int, void *, int, void *, int, void *, int,
19+
void *, int);
20+
21+
typedef struct {
22+
ngx_int_t status;
23+
} ngx_http_firetail_ctx_t;
24+
25+
ngx_int_t FiretailAccessPhaseHandler(ngx_http_request_t *r) {
26+
// Check if FireTail is enabled for this location; if not, skip this handler
27+
FiretailConfig *location_config = ngx_http_get_module_loc_conf(r, ngx_firetail_module);
28+
if (location_config->FiretailEnabled == 0) {
29+
return NGX_DECLINED;
30+
}
31+
32+
ngx_int_t rc;
33+
ngx_http_firetail_ctx_t *ctx;
34+
35+
if (r != r->main) {
36+
return NGX_DECLINED;
37+
}
38+
39+
ctx = ngx_http_get_module_ctx(r, ngx_firetail_module);
40+
41+
if (ctx) {
42+
return ctx->status;
43+
}
44+
45+
ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_firetail_ctx_t));
46+
if (ctx == NULL) {
47+
return NGX_ERROR;
48+
}
49+
50+
ctx->status = NGX_DONE;
51+
52+
rc = ngx_http_read_client_request_body(r, FiretailClientBodyHandler);
53+
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
54+
return rc;
55+
}
56+
57+
ngx_http_finalize_request(r, NGX_DONE);
58+
return NGX_DONE;
59+
}
60+
61+
static void FiretailClientBodyHandler(ngx_http_request_t *request) {
62+
FiretailClientBodyHandlerInternal(request);
63+
request->preserve_body = 1;
64+
request->write_event_handler = ngx_http_core_run_phases;
65+
ngx_http_core_run_phases(request);
66+
}
67+
68+
static ngx_int_t FiretailClientBodyHandlerInternal(ngx_http_request_t *request) {
69+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "✅️✅️✅️HANDLER INTERNAL✅️✅️✅️");
70+
71+
ngx_chain_t *chain_head;
72+
chain_head = request->request_body->bufs;
73+
74+
// Get our context so we can store the request body data
75+
FiretailFilterContext *ctx = GetFiretailFilterContext(request);
76+
if (ctx == NULL) {
77+
return NGX_ERROR;
78+
}
79+
80+
// get the header values
81+
ngx_list_part_t *part;
82+
ngx_table_elt_t *h;
83+
ngx_uint_t i;
84+
part = &request->headers_in.headers.part;
85+
h = part->elts;
86+
json_object *log_root = json_object_new_object();
87+
for (i = 0;; i++) {
88+
if (i >= part->nelts) {
89+
if (part->next == NULL) {
90+
break;
91+
}
92+
part = part->next;
93+
h = part->elts;
94+
i = 0;
95+
}
96+
json_object *jobj = json_object_new_string((char *)h[i].value.data);
97+
json_object *array = json_object_new_array();
98+
json_object_array_add(array, jobj);
99+
json_object_object_add(log_root, (char *)h[i].key.data, array);
100+
}
101+
ctx->request_headers_json = (u_char *)json_object_to_json_string(log_root);
102+
ctx->request_headers_json_size = strlen((char *)ctx->request_headers_json);
103+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "json value %s", (char *)ctx->request_headers_json);
104+
105+
// Determine the length of the request body chain we've been given
106+
long new_request_body_parts_size = 0;
107+
for (ngx_chain_t *current_chain_link = chain_head; current_chain_link != NULL;
108+
current_chain_link = current_chain_link->next) {
109+
new_request_body_parts_size += current_chain_link->buf->last - current_chain_link->buf->pos;
110+
}
111+
112+
// Take note of the request body size before and after adding the new chain
113+
// & update it in our ctx
114+
long old_request_body_size = ctx->request_body_size;
115+
long new_request_body_size = old_request_body_size + new_request_body_parts_size;
116+
ctx->request_body_size = new_request_body_size;
117+
118+
// Create a new updated body
119+
u_char *updated_request_body = ngx_pcalloc(request->pool, new_request_body_size);
120+
121+
// Copy the body read so far into ctx into our new updated_request_body
122+
u_char *updated_request_body_i = ngx_copy(updated_request_body, ctx->request_body, old_request_body_size);
123+
124+
// Iterate over the chain again and copy all of the buffers over to our new
125+
// request body char*
126+
for (ngx_chain_t *current_chain_link = chain_head; current_chain_link != NULL;
127+
current_chain_link = current_chain_link->next) {
128+
long buffer_length = current_chain_link->buf->last - current_chain_link->buf->pos;
129+
updated_request_body_i = ngx_copy(updated_request_body_i, current_chain_link->buf->pos, buffer_length);
130+
}
131+
132+
// Update the ctx with the new updated body
133+
ctx->request_body = updated_request_body;
134+
135+
// Get the main config so we can check if we have 404s disabled from the middleware
136+
FiretailConfig *main_config = ngx_http_get_module_main_conf(request, ngx_firetail_module);
137+
138+
// run the validation
139+
void *validator_module = dlopen("/etc/nginx/modules/firetail-validator.so", RTLD_LAZY);
140+
if (!validator_module) {
141+
return NGX_ERROR;
142+
}
143+
144+
ValidateRequestBody request_body_validator = (ValidateRequestBody)dlsym(validator_module, "ValidateRequestBody");
145+
char *error;
146+
if ((error = dlerror()) != NULL) {
147+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "Failed to load ValidateRequestBody: %s", error);
148+
exit(1);
149+
}
150+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "Validating request body...");
151+
152+
struct ValidateRequestBody_return validation_result = request_body_validator(
153+
main_config->FiretailAllowUndefinedRoutes.data, main_config->FiretailAllowUndefinedRoutes.len, ctx->request_body,
154+
ctx->request_body_size, request->unparsed_uri.data, request->unparsed_uri.len, request->method_name.data,
155+
request->method_name.len, (char *)ctx->request_headers_json, ctx->request_headers_json_size);
156+
157+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "Validation request result: %d", validation_result.r0);
158+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "Validating request body: %s", validation_result.r1);
159+
160+
// if validation is unsuccessful, return bad request
161+
if (validation_result.r0 > 0)
162+
return FiretailReturnFailedValidationResult(request, NULL, chain_head, validation_result.r1);
163+
164+
dlclose(validator_module);
165+
166+
return NGX_OK; // can be NGX_DECLINED - see ngx_http_mirror_handler_internal
167+
// function in nginx mirror module
168+
}
169+
170+
static ngx_int_t FiretailReturnFailedValidationResult(ngx_http_request_t *request, ngx_buf_t *b,
171+
ngx_chain_t *chain_head, char *error) {
172+
ngx_chain_t out;
173+
char *code;
174+
struct json_object *jobj;
175+
ngx_int_t rc;
176+
177+
FiretailFilterContext *ctx = GetFiretailFilterContext(request);
178+
179+
char empty_json[2] = "{}";
180+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "INCOMING Request Body: %s, json %s", ctx->request_body,
181+
empty_json);
182+
183+
if (b == NULL) {
184+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "Buffer for REQUEST is null", NULL);
185+
186+
// bypass response validation because we no longer need
187+
// to go through response validation
188+
ctx->bypass_response = 0;
189+
if (ctx->request_body || (char *)ctx->request_body != empty_json) {
190+
ctx->bypass_response = 1;
191+
}
192+
ctx->request_result = (u_char *)error;
193+
194+
// response parse the middleware json response
195+
jobj = json_tokener_parse(error);
196+
// Get the string value in "code" json key
197+
code = (char *)json_object_get_string(json_object_object_get(jobj, "code"));
198+
199+
// return and finalize request early since we are not going to send
200+
// it to upstream server
201+
ngx_str_t content_type = ngx_string("application/json");
202+
request->headers_out.content_type = content_type;
203+
// convert "code" which is string to integer (status), example: 200
204+
request->headers_out.status = ngx_atoi((u_char *)code, strlen(code));
205+
request->keepalive = 0;
206+
207+
rc = ngx_http_send_header(request);
208+
if (rc == NGX_ERROR || rc > NGX_OK || request->header_only) {
209+
ngx_http_finalize_request(request, rc);
210+
return NGX_DONE;
211+
}
212+
213+
// allocate buffer in pool
214+
b = ngx_calloc_buf(request->pool);
215+
// set the error as unsigned char
216+
u_char *msg = (u_char *)error;
217+
b->pos = msg;
218+
b->last = msg + strlen((char *)msg);
219+
b->memory = 1;
220+
221+
b->last_buf = 1;
222+
223+
out.buf = b;
224+
out.next = NULL;
225+
226+
rc = ngx_http_output_filter(request, &out);
227+
228+
ngx_http_finalize_request(request, rc);
229+
}
230+
231+
if (request == request->main) {
232+
request->headers_out.content_length_n = b->last - b->pos;
233+
234+
if (request->headers_out.content_length) {
235+
request->headers_out.content_length->hash = 0;
236+
request->headers_out.content_length = NULL;
237+
}
238+
}
239+
240+
out.buf = b;
241+
out.next = NULL;
242+
243+
ngx_log_debug(NGX_LOG_DEBUG, request->connection->log, 0, "Sending next REQUEST body", NULL);
244+
return NGX_OK;
245+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#include <ngx_http.h>
2+
3+
ngx_int_t FiretailAccessPhaseHandler(ngx_http_request_t *r);

src/nginx_module/config

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ ngx_addon_name=ngx_firetail_module
22

33
FIRETAIL_SRCS=" \
44
$ngx_addon_dir/firetail_module.c \
5+
$ngx_addon_dir/firetail_context.c \
6+
$ngx_addon_dir/firetail_directives.c \
57
$ngx_addon_dir/filter_context.c \
6-
$ngx_addon_dir/filter_firetail_send.c \
8+
$ngx_addon_dir/access_phase_handler.c \
79
$ngx_addon_dir/filter_response_body.c \
810
$ngx_addon_dir/filter_headers.c \
911
"
1012

1113
FIRETAIL_DEPS=" \
1214
$ngx_addon_dir/firetail_module.h \
15+
$ngx_addon_dir/firetail_context.h \
16+
$ngx_addon_dir/firetail_directives.h \
1317
$ngx_addon_dir/filter_context.h \
14-
$ngx_addon_dir/filter_firetail_send.h \
18+
$ngx_addon_dir/access_phase_handler.h \
1519
$ngx_addon_dir/filter_response_body.h \
1620
$ngx_addon_dir/filter_headers.h \
1721
"

0 commit comments

Comments
 (0)