Init
This commit is contained in:
commit
21582cc86a
21 changed files with 677 additions and 0 deletions
11
ipscrub/config
Normal file
11
ipscrub/config
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Reference: https://www.nginx.com/resources/wiki/extending/new_config/.
|
||||
|
||||
ngx_module_type=HTTP
|
||||
ngx_module_name=ngx_ipscrub_module
|
||||
ngx_module_deps="$ngx_addon_dir/src/ngx_ipscrub_support.h $ngx_addon_dir/src/ngx_ipscrub_debug.h"
|
||||
ngx_module_srcs="$ngx_addon_dir/src/ngx_ipscrub_module.c $ngx_addon_dir/src/ngx_ipscrub_support.c $ngx_addon_dir/src/ngx_ipscrub_debug.c"
|
||||
ngx_module_libs=SHA1
|
||||
|
||||
. auto/module
|
||||
|
||||
ngx_addon_name=$ngx_module_name
|
18
ipscrub/nginx.conf
Normal file
18
ipscrub/nginx.conf
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
error_log logs/error.log;
|
||||
|
||||
events {}
|
||||
|
||||
http {
|
||||
log_format main '[$time_local] $remote_addr $remote_addr_ipscrub';
|
||||
|
||||
access_log logs/access.log main;
|
||||
|
||||
ipscrub_period_seconds 2;
|
||||
|
||||
server {
|
||||
listen 8081;
|
||||
# listen [::]:8081 ipv6only=off;
|
||||
server_name localhost;
|
||||
}
|
||||
}
|
85
ipscrub/src/ngx_ipscrub_debug.c
Normal file
85
ipscrub/src/ngx_ipscrub_debug.c
Normal file
|
@ -0,0 +1,85 @@
|
|||
// Debug support code for ipscrub module.
|
||||
// Copyright Mason Simon 2018
|
||||
|
||||
#include <ngx_http.h>
|
||||
#include <ngx_crypt.h>
|
||||
#include "ngx_ipscrub_support.h"
|
||||
#include "ngx_ipscrub_debug.h"
|
||||
|
||||
// This function hashes the request URI, for testing.
|
||||
ngx_int_t
|
||||
ngx_http_variable_remote_addr_ipscrub_debug(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
|
||||
{
|
||||
u_char *uri;
|
||||
ngx_int_t rc;
|
||||
u_char *hashed;
|
||||
|
||||
u_char *salt = (u_char *) "{SHA}";
|
||||
|
||||
// ngx_crypt computes the length of its second param using ngx_strlen, which requires a null-terminated string.
|
||||
rc = null_terminate(r->pool, r->uri, &uri);
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
rc = ngx_crypt(r->pool, uri, salt, &hashed);
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
// Strip prefix.
|
||||
u_char *obscured = hashed + (sizeof("{SHA}") - 1);
|
||||
|
||||
v->len = 28; // SHA-1 is 160 bits. Base64 is 6 bits per char. ceil(160/6) = 27, but Base64 always groups into chunks of 4, so 28 chars total.
|
||||
v->valid = 1;
|
||||
v->no_cacheable = 0;
|
||||
v->not_found = 0;
|
||||
v->data = obscured;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
// This function hashes the request URI, for testing, using the first 4 chars, excluding the initial slash, as a salt.
|
||||
ngx_int_t
|
||||
ngx_http_variable_ipscrub_salted_hash_debug(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
|
||||
{
|
||||
ngx_str_t salt;
|
||||
ngx_str_t plaintext;
|
||||
ngx_int_t rc;
|
||||
u_char *combined;
|
||||
u_char *hashed;
|
||||
|
||||
u_int saltlen = 4;
|
||||
|
||||
// First 4 chars of URL, excluding initial "/" are interpreted as the salt--they must be present.
|
||||
if (r->uri.len < saltlen + 1) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
salt.data = r->uri.data + 1;
|
||||
salt.len = saltlen;
|
||||
|
||||
plaintext.data = r->uri.data + saltlen + 1;
|
||||
plaintext.len = r->uri.len - (saltlen + 1);
|
||||
|
||||
// ngx_crypt computes the length of its second param using ngx_strlen, which requires a null-terminated string.
|
||||
rc = concat(r->pool, plaintext, salt, &combined);
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
rc = ngx_crypt(r->pool, combined, (u_char *) "{SHA}", &hashed);
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
// Strip prefix.
|
||||
u_char *obscured = hashed + (sizeof("{SHA}") - 1);
|
||||
|
||||
v->len = 28; // SHA-1 is 160 bits. Base64 is 6 bits per char. ceil(160/6) = 27, but Base64 always groups into chunks of 4, so 28 chars total.
|
||||
v->valid = 1;
|
||||
v->no_cacheable = 0;
|
||||
v->not_found = 0;
|
||||
v->data = obscured;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
14
ipscrub/src/ngx_ipscrub_debug.h
Normal file
14
ipscrub/src/ngx_ipscrub_debug.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Debug support code for ipscrub module.
|
||||
// Copyright Mason Simon 2018
|
||||
|
||||
#ifndef _IPSCRUB_DEBUG_H_INCLUDED_
|
||||
#define _IPSCRUB_DEBUG_H_INCLUDED_
|
||||
|
||||
#include <ngx_core.h>
|
||||
|
||||
ngx_int_t ngx_http_variable_remote_addr_ipscrub_debug(ngx_http_request_t *r,
|
||||
ngx_http_variable_value_t *v, uintptr_t data);
|
||||
ngx_int_t ngx_http_variable_ipscrub_salted_hash_debug(ngx_http_request_t *r,
|
||||
ngx_http_variable_value_t *v, uintptr_t data);
|
||||
|
||||
#endif /* _IPSCRUB_DEBUG_H_INCLUDED_ */
|
191
ipscrub/src/ngx_ipscrub_module.c
Normal file
191
ipscrub/src/ngx_ipscrub_module.c
Normal file
|
@ -0,0 +1,191 @@
|
|||
// nginx module that anonymizes IP addresses (e.g. for logging).
|
||||
// Copyright Mason Simon 2018
|
||||
|
||||
#include <ngx_http.h>
|
||||
#include <ngx_crypt.h>
|
||||
#include <time.h>
|
||||
#include "ngx_ipscrub_support.h"
|
||||
#include "ngx_ipscrub_debug.h"
|
||||
|
||||
typedef struct {
|
||||
ngx_int_t period_seconds;
|
||||
} ngx_ipscrub_conf_t;
|
||||
|
||||
static void * ngx_ipscrub_create_conf(ngx_conf_t *cf);
|
||||
static char * ngx_ipscrub_conf(ngx_conf_t *cf, void *conf);
|
||||
|
||||
static ngx_int_t ngx_http_variable_remote_addr_ipscrub(ngx_http_request_t *r,
|
||||
ngx_http_variable_value_t *v, uintptr_t data);
|
||||
|
||||
static ngx_int_t ngx_ipscrub_init(ngx_conf_t *cf);
|
||||
static ngx_int_t ngx_ipscrub_add_variables(ngx_conf_t *cf);
|
||||
|
||||
static ngx_http_variable_t ngx_http_ipscrub_vars[] = {
|
||||
{ngx_string("remote_addr_ipscrub"), NULL, ngx_http_variable_remote_addr_ipscrub, 0, 0, 0},
|
||||
{ngx_string("ipscrub_hash_debug"), NULL, ngx_http_variable_remote_addr_ipscrub_debug, 0, 0, 0},
|
||||
{ngx_string("ipscrub_salted_hash_debug"), NULL, ngx_http_variable_ipscrub_salted_hash_debug, 0, 0, 0},
|
||||
ngx_http_null_variable};
|
||||
|
||||
|
||||
static ngx_command_t ngx_ipscrub_commands[] = {
|
||||
|
||||
{ ngx_string("ipscrub_period_seconds"),
|
||||
NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
|
||||
ngx_conf_set_num_slot,
|
||||
NGX_HTTP_MAIN_CONF_OFFSET,
|
||||
offsetof(ngx_ipscrub_conf_t, period_seconds),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
// Globals.
|
||||
const int default_period_seconds = 10 * 60; // Period between salt changes.
|
||||
time_t period_start = -1;
|
||||
long nonce = -1; // Input to salt generation.
|
||||
|
||||
|
||||
static ngx_http_module_t ngx_ipscrub_module_ctx = {
|
||||
ngx_ipscrub_init, /* preconfiguration */
|
||||
NULL, /* postconfiguration */
|
||||
|
||||
ngx_ipscrub_create_conf, /* create main configuration */
|
||||
ngx_ipscrub_conf, /* init main configuration */
|
||||
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
|
||||
NULL, /* create location configuration */
|
||||
NULL /* merge location configuration */
|
||||
};
|
||||
|
||||
|
||||
ngx_module_t ngx_ipscrub_module = {
|
||||
NGX_MODULE_V1,
|
||||
&ngx_ipscrub_module_ctx, /* module context */
|
||||
ngx_ipscrub_commands, /* module directives */
|
||||
NGX_HTTP_MODULE, /* module type */
|
||||
NULL, /* init master */
|
||||
NULL, /* init module */
|
||||
NULL, /* init process */
|
||||
NULL, /* init thread */
|
||||
NULL, /* exit thread */
|
||||
NULL, /* exit process */
|
||||
NULL, /* exit master */
|
||||
NGX_MODULE_V1_PADDING
|
||||
};
|
||||
|
||||
|
||||
/* Configuration support. */
|
||||
static void *
|
||||
ngx_ipscrub_create_conf(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_ipscrub_conf_t *icf;
|
||||
|
||||
icf = ngx_pcalloc(cf->pool, sizeof(ngx_ipscrub_conf_t));
|
||||
if (icf == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
icf->period_seconds = NGX_CONF_UNSET;
|
||||
|
||||
return icf;
|
||||
}
|
||||
|
||||
static char *
|
||||
ngx_ipscrub_conf(ngx_conf_t *cf, void *conf)
|
||||
{
|
||||
ngx_ipscrub_conf_t *icf = conf;
|
||||
|
||||
ngx_conf_init_value(icf->period_seconds, default_period_seconds);
|
||||
|
||||
return NGX_CONF_OK;
|
||||
}
|
||||
|
||||
|
||||
/* Variable support. */
|
||||
static ngx_int_t
|
||||
ngx_ipscrub_init(ngx_conf_t *cf)
|
||||
{
|
||||
return ngx_ipscrub_add_variables(cf);
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_ipscrub_add_variables(ngx_conf_t *cf)
|
||||
{
|
||||
ngx_http_variable_t *var, *v;
|
||||
|
||||
for (v = ngx_http_ipscrub_vars; v->name.len; v++)
|
||||
{
|
||||
var = ngx_http_add_variable(cf, &v->name, v->flags);
|
||||
if (var == NULL)
|
||||
{
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
var->get_handler = v->get_handler;
|
||||
var->data = v->data;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_variable_remote_addr_ipscrub(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
|
||||
{
|
||||
ngx_int_t rc;
|
||||
u_char *hashed;
|
||||
u_char *combined;
|
||||
ngx_str_t salt;
|
||||
u_int len;
|
||||
|
||||
// Get period_seconds from current configuration.
|
||||
ngx_ipscrub_conf_t *icf;
|
||||
icf = ngx_http_get_module_main_conf(r, ngx_ipscrub_module);
|
||||
|
||||
// Regenerate salt if past end of period.
|
||||
time_t now = time(NULL);
|
||||
if (period_start == -1 || now - period_start > icf->period_seconds) {
|
||||
nonce = ngx_random();
|
||||
// TODO: actually calculate when period_start should have been.
|
||||
period_start = now;
|
||||
}
|
||||
|
||||
salt.data = (u_char *) &nonce;
|
||||
salt.len = sizeof(nonce);
|
||||
|
||||
// Although ngx_crypt provides a salted SHA function, specified by a salt beginning with {SSHA}, that function exposes the salt in its result. For our security model, this is inappropriate. Instead, we use the regular nginx SHA function specified by {SHA}, and manually combine the nonce and plaintext.
|
||||
rc = concat(r->pool, r->connection->addr_text, salt, &combined);
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
rc = ngx_crypt(r->pool, combined, (u_char *) "{SHA}", &hashed);
|
||||
if (rc != NGX_OK) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
|
||||
// Strip prefix.
|
||||
u_char *obscured = hashed + (sizeof("{SHA}") - 1);
|
||||
|
||||
// Compute number of bytes to represent hashed address.
|
||||
if (r->connection->addr_text.data[3] == '.' ||
|
||||
r->connection->addr_text.data[2] == '.' ||
|
||||
r->connection->addr_text.data[1] == '.') {
|
||||
// This is an IPv4 address.
|
||||
// IPv4 has a 32-bit address space. Base64 is 6 bits-per-char. ceil(32/6) = 6.
|
||||
len = 6;
|
||||
} else {
|
||||
// This is an IPv6 address.
|
||||
// IPv6 has a 128-bit address space. Base64 is 6 bits-per-char. ceil(128/6) = 22.
|
||||
len = 22;
|
||||
}
|
||||
|
||||
v->len = len;
|
||||
v->valid = 1;
|
||||
v->no_cacheable = 0;
|
||||
v->not_found = 0;
|
||||
v->data = obscured;
|
||||
|
||||
return NGX_OK;
|
||||
}
|
38
ipscrub/src/ngx_ipscrub_support.c
Normal file
38
ipscrub/src/ngx_ipscrub_support.c
Normal file
|
@ -0,0 +1,38 @@
|
|||
// Support library for ipscrub module.
|
||||
// Copyright Mason Simon 2018
|
||||
|
||||
#include "ngx_ipscrub_support.h"
|
||||
|
||||
// null_terminate allocates a new, null-terminated string based on input.
|
||||
ngx_int_t null_terminate(ngx_pool_t *pool, ngx_str_t input, u_char **out)
|
||||
{
|
||||
*out = ngx_pnalloc(pool, input.len + 1);
|
||||
if (*out == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
(void) ngx_cpymem(*out, input.data, input.len);
|
||||
(*out)[input.len] = '\0';
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
// concat concatenates prefix and suffix then null-terminates the result.
|
||||
ngx_int_t concat(ngx_pool_t *pool, ngx_str_t prefix, ngx_str_t suffix, u_char **out)
|
||||
{
|
||||
// Allocate.
|
||||
*out = ngx_pnalloc(pool, prefix.len + suffix.len + 1);
|
||||
if (*out == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
// Write prefix.
|
||||
(void) ngx_cpymem(*out, prefix.data, prefix.len);
|
||||
|
||||
// Write suffix.
|
||||
(void) ngx_cpymem(*out + prefix.len, suffix.data, suffix.len);
|
||||
|
||||
// Terminate.
|
||||
(*out)[prefix.len + suffix.len] = '\0';
|
||||
|
||||
return NGX_OK;
|
||||
}
|
12
ipscrub/src/ngx_ipscrub_support.h
Normal file
12
ipscrub/src/ngx_ipscrub_support.h
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Support library for ipscrub module.
|
||||
// Copyright Mason Simon 2018
|
||||
|
||||
#ifndef _IPSCRUB_SUPPORT_H_INCLUDED_
|
||||
#define _IPSCRUB_SUPPORT_H_INCLUDED_
|
||||
|
||||
#include <ngx_core.h>
|
||||
|
||||
ngx_int_t null_terminate(ngx_pool_t *pool, ngx_str_t input, u_char **hashed);
|
||||
ngx_int_t concat(ngx_pool_t *pool, ngx_str_t prefix, ngx_str_t suffix, u_char **out);
|
||||
|
||||
#endif /* _IPSCRUB_SUPPORT_H_INCLUDED_ */
|
22
ipscrub/test/dynmodule/nginx.conf
Normal file
22
ipscrub/test/dynmodule/nginx.conf
Normal file
|
@ -0,0 +1,22 @@
|
|||
|
||||
load_module ngx_ipscrub_module.so;
|
||||
|
||||
error_log logs/error.log;
|
||||
|
||||
events {}
|
||||
|
||||
http {
|
||||
log_format main '$remote_addr_ipscrub';
|
||||
|
||||
access_log logs/access.log main;
|
||||
|
||||
ipscrub_period_seconds 2;
|
||||
|
||||
server {
|
||||
# listen 8081;
|
||||
# listen [::]:8081 ipv6only=off;
|
||||
listen 8081;
|
||||
listen [::]:8081;
|
||||
server_name localhost;
|
||||
}
|
||||
}
|
7
ipscrub/test/dynmodule/test.sh
Normal file
7
ipscrub/test/dynmodule/test.sh
Normal file
|
@ -0,0 +1,7 @@
|
|||
red=$'\e[1;31m'
|
||||
grn=$'\e[1;32m'
|
||||
end=$'\e[0m'
|
||||
|
||||
curl "http://localhost:8081/" > /dev/null 2> /dev/null
|
||||
|
||||
[[ $? == 0 ]] && printf "%s\n" "${grn}PASS${end}" || printf "%s\n" "${red}FAIL${end}"
|
20
ipscrub/test/ipv46hash/nginx.conf
Normal file
20
ipscrub/test/ipv46hash/nginx.conf
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
error_log logs/error.log;
|
||||
|
||||
events {}
|
||||
|
||||
http {
|
||||
log_format main '$remote_addr_ipscrub';
|
||||
|
||||
access_log logs/access.log main;
|
||||
|
||||
ipscrub_period_seconds 2;
|
||||
|
||||
server {
|
||||
# listen 8081;
|
||||
# listen [::]:8081 ipv6only=off;
|
||||
listen 8081;
|
||||
listen [::]:8081;
|
||||
server_name localhost;
|
||||
}
|
||||
}
|
11
ipscrub/test/ipv46hash/test.sh
Normal file
11
ipscrub/test/ipv46hash/test.sh
Normal file
|
@ -0,0 +1,11 @@
|
|||
red=$'\e[1;31m'
|
||||
grn=$'\e[1;32m'
|
||||
end=$'\e[0m'
|
||||
|
||||
curl -6 "http://localhost:8081/" > /dev/null 2> /dev/null
|
||||
output6=$(tail -n 1 dest/logs/access.log)
|
||||
|
||||
curl -4 "http://localhost:8081/" > /dev/null 2> /dev/null
|
||||
output4=$(tail -n 1 dest/logs/access.log)
|
||||
|
||||
[[ "${#output6}" == 22 && "${#output4}" == 6 ]] && printf "%s\n" "${grn}PASS${end}" || printf "%s\n" "${red}FAIL${end}"
|
18
ipscrub/test/saltedsha/nginx.conf
Normal file
18
ipscrub/test/saltedsha/nginx.conf
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
error_log logs/error.log;
|
||||
|
||||
events {}
|
||||
|
||||
http {
|
||||
log_format main '$ipscrub_salted_hash_debug';
|
||||
|
||||
access_log logs/access.log main;
|
||||
|
||||
ipscrub_period_seconds 2;
|
||||
|
||||
server {
|
||||
listen 8081;
|
||||
# listen [::]:8081 ipv6only=off;
|
||||
server_name localhost;
|
||||
}
|
||||
}
|
17
ipscrub/test/saltedsha/test.sh
Normal file
17
ipscrub/test/saltedsha/test.sh
Normal file
|
@ -0,0 +1,17 @@
|
|||
red=$'\e[1;31m'
|
||||
grn=$'\e[1;32m'
|
||||
end=$'\e[0m'
|
||||
|
||||
salt="SALT"
|
||||
input="abcdef"
|
||||
|
||||
curl "http://localhost:8081/$salt$input" > /dev/null 2> /dev/null
|
||||
output=$(tail -n 1 dest/logs/access.log)
|
||||
|
||||
# -n excludes newline
|
||||
# cut -c-40 takes first 40 chars (shasum outputs 160 bits in hex)
|
||||
# xxd converts hex to binary
|
||||
# base64 converts binary to ascii
|
||||
clihash=$(echo -n "$input$salt" | shasum | cut -c-40 | xxd -r -p | base64)
|
||||
|
||||
[[ $clihash == $output ]] && printf "%s\n" "${grn}PASS${end}" || printf "%s\n" "${red}FAIL${end}"
|
18
ipscrub/test/sha1hash/nginx.conf
Normal file
18
ipscrub/test/sha1hash/nginx.conf
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
error_log logs/error.log;
|
||||
|
||||
events {}
|
||||
|
||||
http {
|
||||
log_format main '$ipscrub_hash_debug';
|
||||
|
||||
access_log logs/access.log main;
|
||||
|
||||
ipscrub_period_seconds 2;
|
||||
|
||||
server {
|
||||
listen 8081;
|
||||
# listen [::]:8081 ipv6only=off;
|
||||
server_name localhost;
|
||||
}
|
||||
}
|
16
ipscrub/test/sha1hash/test.sh
Normal file
16
ipscrub/test/sha1hash/test.sh
Normal file
|
@ -0,0 +1,16 @@
|
|||
red=$'\e[1;31m'
|
||||
grn=$'\e[1;32m'
|
||||
end=$'\e[0m'
|
||||
|
||||
input="asdf"
|
||||
|
||||
curl "http://localhost:8081/$input" > /dev/null 2> /dev/null
|
||||
output=$(tail -n 1 dest/logs/access.log)
|
||||
|
||||
# -n excludes newline
|
||||
# cut -c-40 takes first 40 chars (shasum outputs 160 bits in hex)
|
||||
# xxd converts hex to binary
|
||||
# base64 converts binary to ascii
|
||||
clihash=$(echo -n "/$input" | shasum | cut -c-40 | xxd -r -p | base64)
|
||||
|
||||
[[ $clihash == $output ]] && printf "%s\n" "${grn}PASS${end}" || printf "%s\n" "${red}FAIL${end}"
|
Loading…
Add table
Add a link
Reference in a new issue