Skip to content

Commit f2bb048

Browse files
authored
Merge branch 'main' into feat/api-ergonomics
2 parents ea4fb4c + 9a55fe7 commit f2bb048

4 files changed

Lines changed: 248 additions & 0 deletions

File tree

.github/workflows/make-install.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,19 @@ jobs:
4343
- name: Install
4444
run: |
4545
sudo make V=1 install
46+
- name: Run CRS tests with go-ftw
47+
if: runner.os == 'Linux'
48+
run: |
49+
sudo apt-get install -y libmicrohttpd-dev
50+
sudo ldconfig
51+
# renovate: depName=coreruleset/go-ftw datasource=github-releases
52+
go install github.com/coreruleset/go-ftw/v2@v2.1.0
53+
# renovate: datasource=github-releases depName=coreruleset/coreruleset
54+
CRS_VERSION="4.25.0"
55+
curl -sSL "https://github.com/coreruleset/coreruleset/archive/refs/tags/v${CRS_VERSION}.tar.gz" -o /tmp/crs.tar.gz
56+
tar xzf /tmp/crs.tar.gz -C /tmp
57+
gcc -o tests/coraza_httpd tests/coraza_httpd.c $(pkg-config --cflags --libs libmicrohttpd) -lcoraza -I/usr/local/include -L/usr/local/lib
58+
printf "include coraza.conf\ninclude /tmp/coreruleset-${CRS_VERSION}/crs-setup.conf.example\ninclude /tmp/coreruleset-${CRS_VERSION}/plugins/empty-before.conf\ninclude /tmp/coreruleset-${CRS_VERSION}/rules/*.conf\ninclude /tmp/coreruleset-${CRS_VERSION}/plugins/empty-after.conf\n" > tests/coraza_includes.conf
59+
LD_LIBRARY_PATH=/usr/local/lib tests/coraza_httpd tests/coraza_includes.conf 8080 &
60+
for i in $(seq 1 10); do curl -sf http://127.0.0.1:8080/ > /dev/null 2>&1 && break; sleep 1; done
61+
~/go/bin/go-ftw run --config tests/ftw.yml --dir /tmp/coreruleset-${CRS_VERSION}/tests/regression/tests/ --cloud --exclude "^920"

tests/coraza.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
SecRuleEngine On
2+
SecRequestBodyAccess On
3+
SecResponseBodyAccess On
4+
SecResponseBodyMimeType text/plain text/html text/xml application/json
5+
SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.blocking_paranoia_level=4,setvar:tx.detection_paranoia_level=4,setvar:tx.crs_validate_utf8_encoding=1,setvar:tx.arg_name_length=100,setvar:tx.arg_length=400,setvar:tx.total_arg_length=64000,setvar:tx.max_num_args=255,setvar:tx.max_file_size=64100,setvar:tx.combined_file_sizes=65535"

tests/coraza_httpd.c

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* coraza_httpd - minimal HTTP server with Coraza WAF
3+
* Uses libmicrohttpd + libcoraza for standalone FTW testing
4+
*/
5+
6+
#include <stdio.h>
7+
#include <stdlib.h>
8+
#include <string.h>
9+
#include <signal.h>
10+
#include <microhttpd.h>
11+
#include <coraza/coraza.h>
12+
13+
static coraza_waf_t waf = 0;
14+
static volatile int running = 1;
15+
16+
static void handle_signal(int sig) {
17+
(void)sig;
18+
running = 0;
19+
}
20+
21+
struct connection_info {
22+
char *data;
23+
size_t data_len;
24+
};
25+
26+
static enum MHD_Result header_iterator(void *cls,
27+
enum MHD_ValueKind kind, const char *key, const char *value)
28+
{
29+
(void)kind;
30+
coraza_transaction_t tx = (coraza_transaction_t)(uintptr_t)cls;
31+
if (key && value) {
32+
coraza_add_request_header(tx, (char*)key, strlen(key), (char*)value, strlen(value));
33+
}
34+
return MHD_YES;
35+
}
36+
37+
static enum MHD_Result handle_request(void *cls,
38+
struct MHD_Connection *connection,
39+
const char *url, const char *method, const char *version,
40+
const char *upload_data, size_t *upload_data_size,
41+
void **con_cls)
42+
{
43+
(void)cls;
44+
45+
/* First call: set up connection info */
46+
if (*con_cls == NULL) {
47+
struct connection_info *ci = calloc(1, sizeof(struct connection_info));
48+
*con_cls = ci;
49+
return MHD_YES;
50+
}
51+
52+
struct connection_info *ci = *con_cls;
53+
54+
/* Accumulate POST data */
55+
if (*upload_data_size > 0) {
56+
char *tmp = realloc(ci->data, ci->data_len + *upload_data_size + 1);
57+
if (!tmp) return MHD_NO;
58+
ci->data = tmp;
59+
memcpy(ci->data + ci->data_len, upload_data, *upload_data_size);
60+
ci->data_len += *upload_data_size;
61+
ci->data[ci->data_len] = '\0';
62+
*upload_data_size = 0;
63+
return MHD_YES;
64+
}
65+
66+
/* All data received, process with Coraza */
67+
coraza_transaction_t tx = coraza_new_transaction(waf);
68+
if (tx == 0) return MHD_NO;
69+
70+
/* Phase 0: connection */
71+
coraza_process_connection(tx, "127.0.0.1", 12345, "127.0.0.1", 80);
72+
73+
/* Strip HTTP/ prefix for version */
74+
const char *proto = "1.1";
75+
if (version && strncmp(version, "HTTP/", 5) == 0)
76+
proto = version + 5;
77+
coraza_process_uri(tx, (char*)url, (char*)method, (char*)proto);
78+
coraza_intervention_t *it = coraza_intervention(tx);
79+
int denied = (it && it->status >= 400);
80+
int deny_status = it ? it->status : 0;
81+
if (it) coraza_free_intervention(it);
82+
83+
/* Phase 1: request headers */
84+
if (!denied) {
85+
MHD_get_connection_values(connection, MHD_HEADER_KIND,
86+
header_iterator, (void*)(uintptr_t)tx);
87+
88+
coraza_process_request_headers(tx);
89+
it = coraza_intervention(tx);
90+
if (it && it->status >= 400) { denied = 1; deny_status = it->status; }
91+
if (it) coraza_free_intervention(it);
92+
}
93+
94+
/* Phase 2: request body */
95+
if (!denied && ci->data) {
96+
coraza_append_request_body(tx, (unsigned char*)ci->data, ci->data_len);
97+
}
98+
if (!denied) {
99+
coraza_process_request_body(tx);
100+
it = coraza_intervention(tx);
101+
if (it && it->status >= 400) { denied = 1; deny_status = it->status; }
102+
if (it) coraza_free_intervention(it);
103+
}
104+
105+
/* Phase 3: response headers */
106+
if (!denied) {
107+
coraza_add_response_header(tx, "Content-Type", 12, "text/plain", 10);
108+
coraza_process_response_headers(tx, 200, "HTTP/1.1");
109+
it = coraza_intervention(tx);
110+
if (it && it->status >= 400) { denied = 1; deny_status = it->status; }
111+
if (it) coraza_free_intervention(it);
112+
}
113+
114+
/* Phase 4: response body */
115+
const char *resp_body;
116+
size_t resp_len;
117+
if (denied) {
118+
resp_body = "Access Denied\n";
119+
resp_len = 14;
120+
} else if (strcmp(url, "/reflect") == 0 && ci->data) {
121+
resp_body = ci->data;
122+
resp_len = ci->data_len;
123+
} else {
124+
resp_body = "OK\n";
125+
resp_len = 3;
126+
}
127+
128+
if (!denied) {
129+
coraza_append_response_body(tx, (unsigned char*)resp_body, resp_len);
130+
coraza_process_response_body(tx);
131+
it = coraza_intervention(tx);
132+
if (it && it->status >= 400) { denied = 1; deny_status = it->status; }
133+
if (it) coraza_free_intervention(it);
134+
}
135+
136+
/* Phase 5: logging */
137+
coraza_process_logging(tx);
138+
it = coraza_intervention(tx);
139+
if (it) coraza_free_intervention(it);
140+
141+
/* Send response */
142+
int status = denied ? deny_status : 200;
143+
if (denied) {
144+
resp_body = "Access Denied\n";
145+
resp_len = 14;
146+
}
147+
148+
struct MHD_Response *response = MHD_create_response_from_buffer(
149+
resp_len, (void*)resp_body, MHD_RESPMEM_MUST_COPY);
150+
if (!response) {
151+
coraza_free_transaction(tx);
152+
return MHD_NO;
153+
}
154+
enum MHD_Result ret = MHD_queue_response(connection, status, response);
155+
MHD_destroy_response(response);
156+
157+
coraza_free_transaction(tx);
158+
159+
return ret;
160+
}
161+
162+
static void request_completed(void *cls, struct MHD_Connection *connection,
163+
void **con_cls, enum MHD_RequestTerminationCode toe)
164+
{
165+
(void)cls; (void)connection; (void)toe;
166+
struct connection_info *ci = *con_cls;
167+
if (ci) {
168+
free(ci->data);
169+
free(ci);
170+
}
171+
}
172+
173+
int main(int argc, char **argv) {
174+
const char *config_file = "coraza_includes.conf";
175+
int port = 8080;
176+
177+
if (argc > 1) config_file = argv[1];
178+
if (argc > 2) port = atoi(argv[2]);
179+
180+
/* Create WAF */
181+
coraza_waf_config_t config = coraza_new_waf_config();
182+
if (coraza_rules_add_file(config, (char*)config_file) < 0) {
183+
fprintf(stderr, "Failed to load rules from %s\n", config_file);
184+
coraza_free_waf_config(config);
185+
return 1;
186+
}
187+
188+
char *err = NULL;
189+
waf = coraza_new_waf(config, &err);
190+
coraza_free_waf_config(config);
191+
if (waf == 0) {
192+
fprintf(stderr, "Failed to create WAF: %s\n", err ? err : "unknown");
193+
return 1;
194+
}
195+
printf("WAF loaded, %d rules\n", coraza_rules_count(waf));
196+
197+
/* Start HTTP server */
198+
struct MHD_Daemon *daemon = MHD_start_daemon(
199+
MHD_USE_INTERNAL_POLLING_THREAD,
200+
port, NULL, NULL,
201+
&handle_request, NULL,
202+
MHD_OPTION_NOTIFY_COMPLETED, &request_completed, NULL,
203+
MHD_OPTION_CLIENT_DISCIPLINE_LVL, (int)-1,
204+
MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int)5,
205+
MHD_OPTION_END);
206+
207+
if (!daemon) {
208+
fprintf(stderr, "Failed to start HTTP server\n");
209+
coraza_free_waf(waf);
210+
return 1;
211+
}
212+
213+
signal(SIGINT, handle_signal);
214+
signal(SIGTERM, handle_signal);
215+
216+
printf("Coraza HTTP server listening on port %d\n", port);
217+
218+
while (running) sleep(1);
219+
220+
MHD_stop_daemon(daemon);
221+
coraza_free_waf(waf);
222+
return 0;
223+
}

tests/ftw.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
testoverride:
2+
input:
3+
dest_addr: 127.0.0.1
4+
port: 8080

0 commit comments

Comments
 (0)