beeta/openresty/lua/fingerprint.lua

72 lines
2 KiB
Lua
Raw Permalink Normal View History

2026-01-05 22:54:27 -05:00
-- Server-side fingerprint generation
-- Generates a consistent fingerprint from IP + User-Agent + Accept-Language
-- This cannot be spoofed by clients as it's computed server-side
local resty_sha256 = require "resty.sha256"
local str = require "resty.string"
local _M = {}
-- Cache fingerprints to avoid recomputation (10 min TTL)
local fingerprint_cache = ngx.shared.fingerprints
-- Generate a fingerprint from request characteristics
-- Uses: Client IP, User-Agent, Accept-Language
function _M.generate(custom_ip)
-- Get client IP (respects X-Real-IP set by upstream proxy)
local client_ip = custom_ip or ngx.var.remote_addr
-- Get User-Agent (empty string if not present)
local user_agent = ngx.var.http_user_agent or ""
-- Get Accept-Language for additional entropy
local accept_lang = ngx.var.http_accept_language or ""
-- Create a cache key from the raw inputs
local cache_key = client_ip .. "|" .. user_agent .. "|" .. accept_lang
-- Check cache first
if fingerprint_cache then
local cached = fingerprint_cache:get(cache_key)
if cached then
return cached
end
end
-- Generate SHA256 hash
local sha256 = resty_sha256:new()
if not sha256 then
ngx.log(ngx.ERR, "Failed to create SHA256 instance")
return nil
end
-- Hash the combined data
sha256:update(client_ip)
sha256:update("|")
sha256:update(user_agent)
sha256:update("|")
sha256:update(accept_lang)
local digest = sha256:final()
local fingerprint = str.to_hex(digest)
-- Cache for 10 minutes (600 seconds)
if fingerprint_cache then
fingerprint_cache:set(cache_key, fingerprint, 600)
end
return fingerprint
end
-- Inject fingerprint header for proxied requests
-- Call this in access_by_lua_block before proxy_pass
function _M.inject_header()
local fp = _M.generate()
if fp then
ngx.req.set_header("X-Server-Fingerprint", fp)
end
return fp
end
return _M