mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
ssl refactoring - wip
This commit is contained in:
parent
c5d9c6936b
commit
946e292b3c
7 changed files with 420 additions and 16 deletions
|
|
@ -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",
|
||||
|
|
|
|||
94
src/common/confs/server-http/ssl-certificate-lua.conf
Normal file
94
src/common/confs/server-http/ssl-certificate-lua.conf
Normal 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
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
296
src/common/core/customcert/customcert.lua.wip
Normal file
296
src/common/core/customcert/customcert.lua.wip
Normal 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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue