192 lines
5.6 KiB
C
192 lines
5.6 KiB
C
// 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;
|
|
}
|