ssl refactoring - wip

This commit is contained in:
fl0ppy-d1sk 2023-12-15 17:52:19 +01:00
parent c5d9c6936b
commit 946e292b3c
No known key found for this signature in database
GPG key ID: 93EE47CC3D061500
7 changed files with 420 additions and 16 deletions

View file

@ -13,6 +13,7 @@ function plugin:initialize(id, ctx)
local current_phase = ngx.get_phase()
for _, check_phase in ipairs {
"set",
"ssl_certificate"
"access",
"content",
"header_filter",

View file

@ -0,0 +1,94 @@
ssl_certificate_by_lua_block {
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
local utils = require "bunkerweb.utils"
local cdatastore = require "bunkerweb.datastore"
local cclusterstore = require "bunkerweb.clusterstore"
local cjson = require "cjson"
-- Don't process internal requests
local logger = clogger:new("SSL-CERTIFICATE")
if ngx.req.is_internal() then
logger:log(ngx.INFO, "skipped ssl_certificate phase because request is internal")
return true
end
-- Start access phase
local datastore = cdatastore:new()
logger:log(ngx.INFO, "ssl_certificate phase started")
-- Fill ctx
logger:log(ngx.INFO, "filling ngx.ctx ...")
local ok, ret, errors, ctx = helpers.fill_ctx()
if not ok then
logger:log(ngx.ERR, "fill_ctx() failed : " .. ret)
elseif errors then
for i, error in ipairs(errors) do
logger:log(ngx.ERR, "fill_ctx() error " .. tostring(i) .. " : " .. error)
end
end
logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
-- Get plugins order
local order, err = datastore:get("plugins_order", true)
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
return
end
-- Call ssl_certificate() methods
logger:log(ngx.INFO, "calling ssl_certificate() methods of plugins ...")
local status = nil
local redirect = nil
for i, plugin_id in ipairs(order.ssl_certificate) do
-- Require call
local plugin_lua, err = helpers.require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ngx.ERR, err)
elseif plugin_lua == nil then
logger:log(ngx.INFO, err)
else
-- Check if plugin has access method
if plugin_lua.ssl_certificate ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua, ctx)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
local ok, ret = helpers.call_plugin(plugin_obj, "ssl_certificate")
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin_id .. ":ssl_certificate() call failed : " .. ret.msg)
else
logger:log(ngx.INFO, plugin_id .. ":ssl_certificate() call successful : " .. ret.msg)
if ret.cert then
end
end
end
else
logger:log(ngx.INFO, "skipped execution of " .. plugin_id .. " because method access() is not defined")
end
end
end
logger:log(ngx.INFO, "called access() methods of plugins")
-- Save ctx
ngx.ctx = ctx
logger:log(ngx.INFO, "access phase ended")
-- Return status if needed
if status then
return ngx.exit(status)
end
-- Redirect if needed
if redirect then
return ngx.redirect(redirect)
end
return true
}

View file

@ -1,7 +1,6 @@
{% set os_path = import("os.path") %}
{% if USE_CUSTOM_SSL == "yes" %}
{% if os_path.isfile("/var/cache/bunkerweb/customcert/cert.pem") and os_path.isfile("/var/cache/bunkerweb/customcert/key.pem") or os_path.isfile("/var/cache/bunkerweb/customcert/" + SERVER_NAME.split(" ")[0] + "/cert.pem") and os_path.isfile("/var/cache/bunkerweb/customcert/" + SERVER_NAME.split(" ")[0] + "/key.pem") +%}
# listen on HTTPS PORT
listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
@ -10,16 +9,18 @@ listen [::]:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if U
{% endif %}
# TLS config
{% if os_path.isfile("/var/cache/bunkerweb/customcert/" + SERVER_NAME.split(" ")[0] + "/cert.pem") %}
ssl_certificate /var/cache/bunkerweb/customcert/{{ SERVER_NAME.split(" ")[0] }}/cert.pem;
{% else %}
ssl_certificate /var/cache/bunkerweb/customcert/cert.pem;
{% endif %}
{% if os_path.isfile("/var/cache/bunkerweb/customcert/" + SERVER_NAME.split(" ")[0] + "/key.pem") %}
ssl_certificate_key /var/cache/bunkerweb/customcert/{{ SERVER_NAME.split(" ")[0] }}/key.pem;
{% else %}
ssl_certificate_key /var/cache/bunkerweb/customcert/key.pem;
{% endif %}
ssl_certificate /var/cache/bunkerweb/default-server-cert/cert.pem;
ssl_certificate_key /var/cache/bunkerweb/default-server-cert/cert.key;
# {% if os_path.isfile("/var/cache/bunkerweb/customcert/" + SERVER_NAME.split(" ")[0] + "/cert.pem") %}
# ssl_certificate /var/cache/bunkerweb/customcert/{{ SERVER_NAME.split(" ")[0] }}/cert.pem;
# {% else %}
# ssl_certificate /var/cache/bunkerweb/customcert/cert.pem;
# {% endif %}
# {% if os_path.isfile("/var/cache/bunkerweb/customcert/" + SERVER_NAME.split(" ")[0] + "/key.pem") %}
# ssl_certificate_key /var/cache/bunkerweb/customcert/{{ SERVER_NAME.split(" ")[0] }}/key.pem;
# {% else %}
# ssl_certificate_key /var/cache/bunkerweb/customcert/key.pem;
# {% endif %}
ssl_protocols {{ SSL_PROTOCOLS }};
ssl_prefer_server_ciphers on;
ssl_session_tickets off;

View file

@ -0,0 +1,296 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local customcert = class("customcert", plugin)
function customcert:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "customcert", ctx)
end
function customcert:init_worker()
if utils.has_variable("USE_CUSTOM_SSL", "yes") then
end
end
function customcert:ssl_certificate()
if self.variables["USE_CUSTOM_SSL"] == "yes" then
end
return self:ret(false, "missing instance ID")
end
return customcert
function bunkernet:initialize(ctx)
-- Call parent initialize
plugin.initialize(self, "bunkernet", ctx)
-- Get BunkerNet ID and save info
if ngx.get_phase() ~= "init" and self:is_needed() then
local id, err = self.datastore:get("plugin_bunkernet_id", true)
if id then
self.bunkernet_id = id
self.version = (self.ctx and self.ctx.bw.version) or utils.get_version()
self.integration = (self.ctx and self.ctx.bw.integration) or utils.get_integration()
else
self.logger:log(ngx.ERR, "can't get BunkerNet ID from datastore : " .. err)
end
end
end
function bunkernet:is_needed()
-- Loading case
if self.is_loading then
return false
end
-- Request phases (no default)
if self.is_request and (self.ctx.bw.server_name ~= "_") then
return self.variables["USE_BUNKERNET"] == "yes"
end
-- Other cases : at least one service uses it
local is_needed, err = utils.has_variable("USE_BUNKERNET", "yes")
if is_needed == nil then
self.logger:log(ngx.ERR, "can't check USE_BUNKERNET variable : " .. err)
end
return is_needed
end
function bunkernet:init_worker()
-- Check if needed
if not self:is_needed() then
return self:ret(true, "no service uses BunkerNet, skipping init_worker")
end
-- Check id
if not self.bunkernet_id then
return self:ret(false, "missing instance ID")
end
-- Send ping request
local ok, err, status, _ = self:ping()
if not ok then
return self:ret(false, "error while sending request to API : " .. err)
end
if status ~= 200 then
return self:ret(
false,
"received status " .. tostring(status) .. " from API using instance ID " .. self.bunkernet_id
)
end
self.logger:log(ngx.NOTICE, "connectivity with API using instance ID " .. self.bunkernet_id .. " is successful")
return self:ret(true, "connectivity with API using instance ID " .. self.bunkernet_id .. " is successful")
end
function bunkernet:init()
-- Check if needed
if not self:is_needed() then
return self:ret(true, "no service uses BunkerNet, skipping init")
end
-- Check if instance ID is present
local f, err = io.open("/var/cache/bunkerweb/bunkernet/instance.id", "r")
if not f then
return self:ret(false, "can't read instance id : " .. err)
end
-- Retrieve instance ID
local id = f:read("*all"):gsub("[\r\n]", "")
f:close()
-- Store ID in datastore
local ok, err = self.datastore:set("plugin_bunkernet_id", id, nil, true)
if not ok then
return self:ret(false, "can't save instance ID to the datastore : " .. err)
end
-- Load databases
local ret = true
local i = 0
local db = {
ip = {},
}
local f, err = io.open("/var/cache/bunkerweb/bunkernet/ip.list", "r")
if not f then
ret = false
else
for line in f:lines() do
if (utils.is_ipv4(line) or utils.is_ipv6(line)) and utils.ip_is_global(line) then
table.insert(db.ip, line)
i = i + 1
end
end
end
if not ret then
return self:ret(false, "error while reading database : " .. err)
end
f:close()
local ok, err = self.datastore:set("plugin_bunkernet_db", db, nil, true)
if not ok then
return self:ret(false, "can't store bunkernet database into datastore : " .. err)
end
return self:ret(true, "successfully loaded " .. tostring(i) .. " bad IPs using instance ID " .. id)
end
function bunkernet:access()
-- Check if needed
if not self:is_needed() then
return self:ret(true, "service doesn't use BunkerNet, skipping access")
end
-- Check id
if not self.bunkernet_id then
return self:ret(false, "missing instance ID")
end
-- Check if IP is global
if not self.ctx.bw.ip_is_global then
return self:ret(true, "IP is not global")
end
-- Check if whitelisted
if self.ctx.bw.is_whitelisted == "yes" then
return self:ret(true, "client is whitelisted")
end
-- Extract DB
local db, err = self.datastore:get("plugin_bunkernet_db", true)
if db then
-- Check if is IP is present
if #db.ip > 0 then
-- luacheck: ignore 421
local present, err = utils.is_ip_in_networks(self.ctx.bw.remote_addr, db.ip)
if present == nil then
return self:ret(false, "can't check if ip is in db : " .. err)
end
if present then
return self:ret(true, "ip is in db", utils.get_deny_status(self.ctx))
end
end
else
return self:ret(false, "can't get bunkernet db " .. err)
end
return self:ret(true, "not in db")
end
function bunkernet:log(bypass_checks)
if not bypass_checks then
-- Check if needed
if not self:is_needed() then
return self:ret(true, "service doesn't use BunkerNet, skipping log")
end
-- Check id
if not self.bunkernet_id then
return self:ret(false, "missing instance ID")
end
end
-- Check if IP has been blocked
local reason = utils.get_reason(self.ctx)
if not reason then
return self:ret(true, "ip is not blocked")
end
if reason == "bunkernet" then
return self:ret(true, "skipping report because the reason is bunkernet")
end
-- Check if IP is global
if not self.ctx.bw.ip_is_global then
return self:ret(true, "IP is not global")
end
-- TODO : check if IP has been reported recently
-- luacheck: ignore 212 431
local function report_callback(premature, obj, ip, reason, method, url, headers)
local ok, err, status, _ = obj:report(ip, reason, method, url, headers)
if status == 429 then
obj.logger:log(ngx.WARN, "bunkernet API is rate limiting us")
elseif not ok then
obj.logger:log(ngx.ERR, "can't report IP : " .. err)
else
obj.logger:log(ngx.NOTICE, "successfully reported IP " .. ip .. " (reason : " .. reason .. ")")
end
end
local hdr, err = ngx.timer.at(
0,
report_callback,
self,
self.ctx.bw.remote_addr,
reason,
self.ctx.bw.request_method,
self.ctx.bw.request_uri,
ngx.req.get_headers()
)
if not hdr then
return self:ret(false, "can't create report timer : " .. err)
end
return self:ret(true, "created report timer")
end
function bunkernet:log_default()
-- Check if needed
if not self:is_needed() then
return self:ret(true, "no service uses BunkerNet, skipping log_default")
end
-- Check id
if not self.bunkernet_id then
return self:ret(false, "missing instance ID")
end
-- Check if default server is disabled
local check, err = utils.get_variable("DISABLE_DEFAULT_SERVER", false)
if check == nil then
return self:ret(false, "error while getting variable DISABLE_DEFAULT_SERVER : " .. err)
end
if check ~= "yes" then
return self:ret(true, "default server is not disabled")
end
-- Call log method
return self:log(true)
end
function bunkernet:log_stream()
return self:log()
end
function bunkernet:request(method, url, data)
local httpc, err = http.new()
if not httpc then
return false, "can't instantiate http object : " .. err
end
local all_data = {
id = self.bunkernet_id,
version = self.version,
integration = self.integration,
}
if data then
for k, v in pairs(data) do
all_data[k] = v
end
end
local res, err = httpc:request_uri(self.variables["BUNKERNET_SERVER"] .. url, {
method = method,
body = cjson.encode(all_data),
headers = {
["Content-Type"] = "application/json",
["User-Agent"] = "BunkerWeb/" .. self.version,
},
})
httpc:close()
if not res then
return false, "error while sending request : " .. err
end
if res.status ~= 200 then
return false, "status code != 200", res.status, nil
end
local ok, ret = pcall(cjson.decode, res.body)
if not ok then
return false, "error while decoding json : " .. ret
end
return true, "success", res.status, ret
end
function bunkernet:ping()
return self:request("GET", "/ping", {})
end
function bunkernet:report(ip, reason, method, url, headers)
local data = {
ip = ip,
reason = reason,
method = method,
url = url,
headers = headers,
}
return self:request("POST", "/report", data)
end
return bunkernet

View file

@ -13,8 +13,10 @@ listen [::]:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if U
{% endif %}
# TLS config
ssl_certificate /var/cache/bunkerweb/letsencrypt/etc/live/{{ SERVER_NAME.split(" ")[0] }}/fullchain.pem;
ssl_certificate_key /var/cache/bunkerweb/letsencrypt/etc/live/{{ SERVER_NAME.split(" ")[0] }}/privkey.pem;
ssl_certificate /var/cache/bunkerweb/default-server-cert/cert.pem;
ssl_certificate_key /var/cache/bunkerweb/default-server-cert/cert.key;
#ssl_certificate /var/cache/bunkerweb/letsencrypt/etc/live/{{ SERVER_NAME.split(" ")[0] }}/fullchain.pem;
#ssl_certificate_key /var/cache/bunkerweb/letsencrypt/etc/live/{{ SERVER_NAME.split(" ")[0] }}/privkey.pem;
ssl_protocols {{ SSL_PROTOCOLS }};
ssl_prefer_server_ciphers on;
ssl_session_tickets off;

View file

@ -7,8 +7,16 @@
"bunkernet",
"limit"
],
"init_worker": ["redis", "bunkernet", "dnsbl"],
"init_worker": [
"redis",
"bunkernet",
"dnsbl",
"customcert",
"letsencrypt",
"selfsigned"
],
"set": ["sessions", "whitelist"],
"ssl_certificate": ["customcert", "letsencrypt", "selfsigned"],
"access": [
"whitelist",
"letsencrypt",

View file

@ -7,8 +7,10 @@ listen [::]:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if U
{% endif %}
# TLS config
ssl_certificate /var/cache/bunkerweb/selfsigned/{{ SERVER_NAME.split(" ")[0] }}.pem;
ssl_certificate_key /var/cache/bunkerweb/selfsigned/{{ SERVER_NAME.split(" ")[0] }}.key;
ssl_certificate /var/cache/bunkerweb/default-server-cert/cert.pem;
ssl_certificate_key /var/cache/bunkerweb/default-server-cert/cert.key;
# ssl_certificate /var/cache/bunkerweb/selfsigned/{{ SERVER_NAME.split(" ")[0] }}.pem;
# ssl_certificate_key /var/cache/bunkerweb/selfsigned/{{ SERVER_NAME.split(" ")[0] }}.key;
ssl_protocols {{ SSL_PROTOCOLS }};
ssl_prefer_server_ciphers on;
ssl_session_tickets off;