Merge pull request #485 from bunkerity/dev

Merge branch "dev" into branch "ui"
This commit is contained in:
Théophile Diot 2023-05-22 09:57:51 -04:00 committed by GitHub
commit eda275589d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
69 changed files with 2031 additions and 563 deletions

3
TODO
View File

@ -1,6 +1,5 @@
- Ansible
- Vagrant
- Plugins
- sessions helpers in utils
- sessions security : check IP address, check UA, ...
- Find a way to do rdns in background
- fix db warnings (Got an error reading communication packets)

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 91 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -253,14 +253,19 @@ That kind of security is implemented but not enabled by default in BunkerWeb and
Here is the list of related settings :
| Setting | Default | Description |
| :--------------------------------------------------------: | :----------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `USE_ANTIBOT` | `no` | Accepted values to enable Antibot feature : `cookie`, `javascript`, `captcha`, `hcaptcha` and `recaptcha`. |
| `ANTIBOT_URI` | `/challenge` | URI that clients will be redirected to in order to solve the challenge. Be sure that it isn't used in your web application. |
| `ANTIBOT_SESSION_SECRET` | `random` | The secret used to encrypt cookies when using Antibot. The special value `random` will generate one for you. Be sure to set it when you use a clustered integration (32 chars). |
| `ANTIBOT_HCAPTCHA_SITEKEY` and `ANTIBOT_RECAPTCHA_SITEKEY` | | The Sitekey value to use when `USE_ANTIBOT` is set to `hcaptcha` or `recaptcha`. |
| `ANTIBOT_HCAPTCHA_SECRET` and `ANTIBOT_RECAPTCHA_SECRET` | | The Secret value to use when `USE_ANTIBOT` is set to `hcaptcha` or `recaptcha`. |
| `ANTIBOT_RECAPTCHA_SCORE` | `0.7` | The minimum score that clients must have when `USE_ANTIBOT` is set to `recaptcha`. |
| Setting | Default | Context |Multiple| Description |
|---------------------------|------------|---------|--------|------------------------------------------------------------------------------------------------------------------------------|
|`USE_ANTIBOT` |`no` |multisite|no |Activate antibot feature. |
|`ANTIBOT_URI` |`/challenge`|multisite|no |Unused URI that clients will be redirected to to solve the challenge. |
|`ANTIBOT_RECAPTCHA_SCORE` |`0.7` |multisite|no |Minimum score required for reCAPTCHA challenge. |
|`ANTIBOT_RECAPTCHA_SITEKEY`| |multisite|no |Sitekey for reCAPTCHA challenge. |
|`ANTIBOT_RECAPTCHA_SECRET` | |multisite|no |Secret for reCAPTCHA challenge. |
|`ANTIBOT_HCAPTCHA_SITEKEY` | |multisite|no |Sitekey for hCaptcha challenge. |
|`ANTIBOT_HCAPTCHA_SECRET` | |multisite|no |Secret for hCaptcha challenge. |
|`ANTIBOT_TIME_RESOLVE` |`60` |multisite|no |Maximum time (in seconds) clients have to resolve the challenge. Once this time has passed, a new challenge will be generated.|
|`ANTIBOT_TIME_VALID` |`86400` |multisite|no |Maximum validity time of solved challenges. Once this time has passed, clients will need to resolve a new one. |
Please note that antibot feature is using a cookie to maintain a session with clients. If you are using BunkerWeb in a clustered environment, you will need to set the `SESSIONS_SECRET` and `SESSIONS_NAME` settings to another value than the default one (which is `random`). You will find more info about sessions [here](settings.md#sessions).
## Blacklisting, whitelisting and greylisting

View File

@ -498,6 +498,8 @@ Management of session used by other plugins.
|`SESSIONS_IDLING_TIMEOUT` |`1800` |global |no |Maximum time (in seconds) of inactivity before the session is invalidated. |
|`SESSIONS_ROLLING_TIMEOUT` |`3600` |global |no |Maximum time (in seconds) before a session must be renewed. |
|`SESSIONS_ABSOLUTE_TIMEOUT`|`86400` |global |no |Maximum time (in seconds) before a session is destroyed. |
|`SESSIONS_CHECK_IP` |`yes` |global |no |Destroy session if IP address is different than original one. |
|`SESSIONS_CHECK_USER_AGENT`|`yes` |global |no |Destroy session if User-Agent is different than original one. |
### UI

View File

@ -124,7 +124,7 @@ spec:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
value: "mysql+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
---
apiVersion: apps/v1
kind: Deployment
@ -151,7 +151,7 @@ spec:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
value: "mysql+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
---
apiVersion: apps/v1
kind: Deployment
@ -213,64 +213,6 @@ spec:
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bunkerweb-redis
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: bunkerweb-redis
template:
metadata:
labels:
app: bunkerweb-redis
spec:
containers:
- name: bunkerweb-redis
image: redis:7-alpine
imagePullPolicy: Always
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bunkerweb-db
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: bunkerweb-db
template:
metadata:
labels:
app: bunkerweb-db
spec:
containers:
- name: bunkerweb-db
image: mariadb:10.10
imagePullPolicy: Always
env:
- name: MYSQL_RANDOM_ROOT_PASSWORD
value: "yes"
- name: "MYSQL_DATABASE"
value: "db"
- name: "MYSQL_USER"
value: "bunkerweb"
- name: "MYSQL_PASSWORD"
value: "changeme"
volumeMounts:
- mountPath: "/var/lib/mysql"
name: vol-db
volumes:
- name: vol-db
persistentVolumeClaim:
claimName: pvc-bunkerweb
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: bunkerweb-ui
spec:
@ -300,7 +242,7 @@ spec:
- name: KUBERNETES_MODE
value: "YES"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:testor@svc-bunkerweb-db:3306/db"
value: "mysql+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
---
apiVersion: v1
kind: Service
@ -363,7 +305,6 @@ spec:
resources:
requests:
storage: 5Gi
volumeName: pv-bunkerweb
---
apiVersion: networking.k8s.io/v1
kind: Ingress

View File

@ -124,7 +124,7 @@ spec:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
value: "mysql+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
---
apiVersion: apps/v1
kind: Deployment
@ -150,7 +150,7 @@ spec:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
value: "mysql+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
---
apiVersion: apps/v1
kind: Deployment
@ -257,4 +257,3 @@ spec:
resources:
requests:
storage: 5Gi
volumeName: pv-bunkerweb

View File

@ -124,7 +124,7 @@ spec:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
value: "postgresql://bunkerweb:changeme@svc-bunkerweb-db:5432/db"
---
apiVersion: apps/v1
kind: Deployment
@ -151,7 +151,7 @@ spec:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
value: "postgresql://bunkerweb:changeme@svc-bunkerweb-db:5432/db"
---
apiVersion: apps/v1
kind: Deployment
@ -201,6 +201,8 @@ spec:
value: "bunkerweb"
- name: "POSTGRES_PASSWORD"
value: "changeme"
- name: "PGDATA"
value: "/var/lib/postgresql/data/pgdata"
volumeMounts:
- mountPath: "/var/lib/postgresql/data"
name: vol-db
@ -240,7 +242,7 @@ spec:
- name: KUBERNETES_MODE
value: "YES"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:testor@svc-bunkerweb-db:3306/db"
value: "postgresql://bunkerweb:changeme@svc-bunkerweb-db:5432/db"
---
apiVersion: v1
kind: Service
@ -303,19 +305,6 @@ spec:
resources:
requests:
storage: 5Gi
volumeName: pv-bunkerweb
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-bunkerweb
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
volumeName: pv-bunkerweb
---
apiVersion: networking.k8s.io/v1
kind: Ingress

View File

@ -124,7 +124,7 @@ spec:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
value: "postgresql://bunkerweb:changeme@svc-bunkerweb-db:5432/db"
---
apiVersion: apps/v1
kind: Deployment
@ -150,7 +150,7 @@ spec:
- name: KUBERNETES_MODE
value: "yes"
- name: "DATABASE_URI"
value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db"
value: "postgresql://bunkerweb:changeme@svc-bunkerweb-db:5432/db"
---
apiVersion: apps/v1
kind: Deployment
@ -200,6 +200,8 @@ spec:
value: "bunkerweb"
- name: "POSTGRES_PASSWORD"
value: "changeme"
- name: "PGDATA"
value: "/var/lib/postgresql/data/pgdata"
volumeMounts:
- mountPath: "/var/lib/postgresql/data"
name: vol-db
@ -255,4 +257,3 @@ spec:
resources:
requests:
storage: 5Gi
volumeName: pv-bunkerweb

View File

@ -1,8 +1,11 @@
local class = require "middleclass"
local datastore = require "bunkerweb.datastore"
local utils = require "bunkerweb.utils"
local logger = require "bunkerweb.logger"
local cjson = require "cjson"
local upload = require "resty.upload"
local rsignal = require "resty.signal"
local process = require "ngx.process"
local api = class("api")
@ -10,6 +13,32 @@ api.global = { GET = {}, POST = {}, PUT = {}, DELETE = {} }
function api:initialize()
self.datastore = datastore:new()
self.logger = logger:new("API")
end
function api:log_cmd(cmd, status, stdout, stderr)
local level = ngx.NOTICE
local prefix = "success"
if status ~= 0 then
level = ngx.ERR
prefix = "error"
end
self.logger:log(level, prefix .. " while running command " .. command)
self.logger:log(level, "stdout = " .. stdout)
self.logger:log(level, "stdout = " .. stderr)
end
-- TODO : use this if we switch to OpenResty
function api:cmd(cmd)
-- Non-blocking command
local ok, stdout, stderr, reason, status = shell.run(cmd, nil, 10000)
self.logger:log_cmd(cmd, status, stdout, stderr)
-- Timeout
if ok == nil then
return nil, reason
end
-- Other cases : exit 0, exit !0 and killed by signal
return status == 0, reason, status
end
function api:response(http_status, api_status, msg)
@ -24,19 +53,21 @@ api.global.GET["^/ping$"] = function(self)
end
api.global.POST["^/reload$"] = function(self)
local status = os.execute("nginx -s reload")
if status == 0 then
return self:response(ngx.HTTP_OK, "success", "reload successful")
-- Send HUP signal to master process
local ok, err = rsignal.kill(process.get_master_pid(), "HUP")
if not ok then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "err = " .. err)
end
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
return self:response(ngx.HTTP_OK, "success", "reload successful")
end
api.global.POST["^/stop$"] = function(self)
local status = os.execute("nginx -s quit")
if status == 0 then
return self:response(ngx.HTTP_OK, "success", "stop successful")
-- Send QUIT signal to master process
local ok, err = rsignal.kill(process.get_master_pid(), "QUIT")
if not ok then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "err = " .. err)
end
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
return self:response(ngx.HTTP_OK, "success", "stop successful")
end
api.global.POST["^/confs$"] = function(self)
@ -74,13 +105,15 @@ api.global.POST["^/confs$"] = function(self)
end
file:flush()
file:close()
local status = os.execute("rm -rf " .. destination .. "/*")
if status ~= 0 then
return self:response(ngx.HTTP_BAD_REQUEST, "error", "can't remove old files")
end
status = os.execute("tar xzf " .. tmp .. " -C " .. destination)
if status ~= 0 then
return self:response(ngx.HTTP_BAD_REQUEST, "error", "can't extract archive")
local cmds = {
"rm -rf " .. destination .. "/*",
"tar xzf " .. tmp .. " -C " .. destination
}
for i, cmd in ipairs(cmds) do
local status = os.execute(cmd)
if status ~= 0 then
return self:response(ngx.HTTP_INTERNAL_SERVER_ERROR, "error", "exit status = " .. tostring(status))
end
end
return self:response(ngx.HTTP_OK, "success", "saved data at " .. destination)
end

View File

@ -1,4 +1,5 @@
local mlcache = require "resty.mlcache"
local clusterstore = require "bunkerweb.clusterstore"
local logger = require "bunkerweb.logger"
local utils = require "bunkerweb.utils"
local class = require "middleclass"
@ -41,17 +42,24 @@ if not cache then
module_logger:log(ngx.ERR, "can't instantiate mlcache : " .. err)
end
function cachestore:initialize(use_redis)
function cachestore:initialize(use_redis, new_cs)
self.cache = cache
self.use_redis = (use_redis and utils.is_cosocket_available()) or false
self.use_redis = use_redis or false
self.logger = module_logger
if new_cs then
self.clusterstore = clusterstore:new(false)
self.shared_cs = false
else
self.clusterstore = utils.get_ctx_obj("clusterstore")
self.shared_cs = true
end
end
function cachestore:get(key)
local callback = function(key)
local callback = function(key, cs)
-- Connect to redis
local clusterstore = require "bunkerweb.clusterstore":new()
local ok, err = clusterstore:connect()
local clusterstore = cs or require "bunkerweb.clusterstore":new(false)
local ok, err, reused = clusterstore:connect()
if not ok then
return nil, "can't connect to redis : " .. err, nil
end
@ -88,8 +96,12 @@ function cachestore:get(key)
return nil, nil, -1
end
local value, err, hit_level
if self.use_redis then
value, err, hit_level = self.cache:get(key, nil, callback, key)
if self.use_redis and utils.is_cosocket_available() then
local cs = nil
if self.shared_cs then
cs = self.clusterstore
end
value, err, hit_level = self.cache:get(key, nil, callback, key, cs)
else
value, err, hit_level = self.cache:get(key, nil, callback_no_miss)
end
@ -101,7 +113,7 @@ function cachestore:get(key)
end
function cachestore:set(key, value, ex)
if self.use_redis then
if self.use_redis and utils.is_cosocket_available() then
local ok, err = self:set_redis(key, value, ex)
if not ok then
self.logger:log(ngx.ERR, err)
@ -121,24 +133,23 @@ end
function cachestore:set_redis(key, value, ex)
-- Connect to redis
local clusterstore = require "bunkerweb.clusterstore":new()
local ok, err = clusterstore:connect()
local ok, err, reused = self.clusterstore:connect()
if not ok then
return false, "can't connect to redis : " .. err
end
-- Set value with ttl
local default_ex = ex or 30
local ok, err = clusterstore:call("set", key, value, "EX", default_ex)
local ok, err = self.clusterstore:call("set", key, value, "EX", default_ex)
if err then
clusterstore:close()
self.clusterstore:close()
return false, "SET failed : " .. err
end
clusterstore:close()
self.clusterstore:close()
return true
end
function cachestore:delete(key, value, ex)
if self.use_redis then
if self.use_redis and utils.is_cosocket_available() then
local ok, err = self.del_redis(key)
if not ok then
self.logger:log(ngx.ERR, err)
@ -153,18 +164,17 @@ end
function cachestore:del_redis(key)
-- Connect to redis
local clusterstore = require "bunkerweb.clusterstore":new()
local ok, err = clusterstore:connect()
local ok, err = self.clusterstore:connect()
if not ok then
return false, "can't connect to redis : " .. err
end
-- Set value with ttl
local ok, err = clusterstore:del(key)
local ok, err = self.clusterstore:del(key)
if err then
clusterstore:close()
self.clusterstore:close()
return false, "DEL failed : " .. err
end
clusterstore:close()
self.clusterstore:close()
return true
end

View File

@ -5,7 +5,7 @@ local redis = require "resty.redis"
local clusterstore = class("clusterstore")
function clusterstore:initialize()
function clusterstore:initialize(pool)
-- Instantiate logger
self.logger = logger:new("CLUSTERSTORE")
-- Get variables
@ -29,12 +29,13 @@ function clusterstore:initialize()
end
-- Don't instantiate a redis object for now
self.redis_client = nil
self.pool = pool == nil or pool
end
function clusterstore:connect()
-- Check if we are already connected
if self.redis_client ~= nil then
return true, "already connected"
if self.redis_client then
return true, "already connected", self.redis_client:get_reused_times()
end
-- Instantiate object
local redis_client, err = redis:new()
@ -42,42 +43,50 @@ function clusterstore:connect()
return false, err
end
-- Set timeouts
redis_client:set_timeouts(tonumber(self.variables["REDIS_TIMEOUT"]), tonumber(self.variables["REDIS_TIMEOUT"]),
tonumber(self.variables["REDIS_TIMEOUT"]))
redis_client:set_timeout(tonumber(self.variables["REDIS_TIMEOUT"]))
-- Connect
local options = {
ssl = self.variables["REDIS_SSL"] == "yes",
pool = "bw",
pool_size = tonumber(self.variables["REDIS_KEEPALIVE_POOL"])
}
if self.pool then
options.pool = "bw-redis"
options.pool_size = tonumber(self.variables["REDIS_KEEPALIVE_POOL"])
end
local ok, err = redis_client:connect(self.variables["REDIS_HOST"], tonumber(self.variables["REDIS_PORT"]), options)
if not ok then
return false, err
end
-- Save client
self.redis_client = redis_client
-- Select database if needed
local times, err = redis_client:get_reused_times()
local times, err = self.redis_client:get_reused_times()
if err then
self:close()
return false, err
end
if times == 0 then
local select, err = redis_client:select(tonumber(self.variables["REDIS_DATABASE"]))
local select, err = self.redis_client:select(tonumber(self.variables["REDIS_DATABASE"]))
if err then
self:close()
return false, err
end
end
return true, "success"
return true, "success", times
end
function clusterstore:close()
if self.redis_client then
-- Equivalent to close but keep a pool of connections
local ok, err = self.redis_client:set_keepalive(tonumber(self.variables["REDIS_KEEPALIVE_IDLE"]),
tonumber(self.variables["REDIS_KEEPALIVE_POOL"]))
self.redis_client = nil
if self.pool then
local ok, err = self.redis_client:set_keepalive(tonumber(self.variables["REDIS_KEEPALIVE_IDLE"]), tonumber(self.variables["REDIS_KEEPALIVE_POOL"]))
self.redis_client = nil
if not ok then
require "bunkerweb.logger":new("clusterstore-close"):log(ngx.ERR, err)
end
return ok, err
end
-- Close
local ok, err = self.redis_client:close()
self.redis_client.redis_client = nil
return ok, err
end
return false, "not connected"

View File

@ -146,45 +146,50 @@ helpers.call_plugin = function(plugin, method)
end
helpers.fill_ctx = function()
-- Check if ctx is already filled
if ngx.ctx.bw then
return true, "already filled"
end
-- Return errors as table
local errors = {}
-- Instantiate bw table
local data = {}
-- Common vars
data.kind = "http"
if ngx.shared.datastore_stream then
data.kind = "stream"
-- Check if ctx is already filled
if not ngx.ctx.bw then
-- Instantiate bw table
local data = {}
-- Common vars
data.kind = "http"
if ngx.shared.datastore_stream then
data.kind = "stream"
end
data.remote_addr = ngx.var.remote_addr
data.uri = ngx.var.uri
data.request_uri = ngx.var.request_uri
data.request_method = ngx.var.request_method
data.http_user_agent = ngx.var.http_user_agent
data.http_host = ngx.var.http_host
data.server_name = ngx.var.server_name
data.http_content_type = ngx.var.http_content_type
data.http_origin = ngx.var.http_origin
-- IP data : global
local ip_is_global, err = utils.ip_is_global(data.remote_addr)
if ip_is_global == nil then
table.insert(errors, "can't check if IP is global : " .. err)
else
data.ip_is_global = ip_is_global
end
-- IP data : v4 / v6
data.ip_is_ipv4 = utils.is_ipv4(data.ip)
data.ip_is_ipv6 = utils.is_ipv6(data.ip)
-- Misc info
data.integration = utils.get_integration()
data.version = utils.get_version()
-- Fill ctx
ngx.ctx.bw = data
end
data.remote_addr = ngx.var.remote_addr
data.uri = ngx.var.uri
data.request_uri = ngx.var.request_uri
data.request_method = ngx.var.request_method
data.http_user_agent = ngx.var.http_user_agent
data.http_host = ngx.var.http_host
data.server_name = ngx.var.server_name
data.http_content_type = ngx.var.http_content_type
data.http_origin = ngx.var.http_origin
-- IP data : global
local ip_is_global, err = utils.ip_is_global(data.remote_addr)
if ip_is_global == nil then
table.insert(errors, "can't check if IP is global : " .. err)
else
data.ip_is_global = ip_is_global
-- Always create new objects for current phases in case of cosockets
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
table.insert(errors, "can't get variable from datastore : " .. err)
end
-- IP data : v4 / v6
data.ip_is_ipv4 = utils.is_ipv4(data.ip)
data.ip_is_ipv6 = utils.is_ipv6(data.ip)
-- Misc info
data.integration = utils.get_integration()
data.version = utils.get_version()
-- Plugins
data.plugins = {}
-- Fill ctx
ngx.ctx.bw = data
ngx.ctx.bw.datastore = require "bunkerweb.datastore":new()
ngx.ctx.bw.clusterstore = require "bunkerweb.clusterstore":new()
ngx.ctx.bw.cachestore = require "bunkerweb.cachestore":new(use_redis == "yes")
return true, "ctx filled", errors
end

View File

@ -1,17 +1,40 @@
local class = require "middleclass"
local logger = require "bunkerweb.logger"
local datastore = require "bunkerweb.datastore"
local cachestore = require "bunkerweb.cachestore"
local clusterstore = require "bunkerweb.clusterstore"
local utils = require "bunkerweb.utils"
local cjson = require "cjson"
local plugin = class("plugin")
function plugin:initialize(id)
-- Store default values
-- Store common, values
self.id = id
self.variables = {}
-- Instantiate objects
self.logger = logger:new(id)
self.datastore = datastore:new()
local multisite = false
local current_phase = ngx.get_phase()
for i, check_phase in ipairs({ "set", "access", "content", "header", "log", "preread", "log_stream", "log_default" }) do
if current_phase == check_phase then
multisite = true
break
end
end
self.is_request = multisite
-- Store common objets
self.logger = logger:new(self.id)
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
if self.is_request then
self.datastore = utils.get_ctx_obj("datastore") or datastore:new()
self.cachestore = utils.get_ctx_obj("cachestore") or cachestore:new(use_redis == "yes", true)
self.clusterstore = utils.get_ctx_obj("clusterstore") or clusterstore:new(false)
else
self.datastore = datastore:new()
self.cachestore = cachestore:new(use_redis == "yes", true)
self.clusterstore = clusterstore:new(false)
end
-- Get metadata
local encoded_metadata, err = self.datastore:get("plugin_" .. id)
if not encoded_metadata then
@ -19,16 +42,8 @@ function plugin:initialize(id)
return
end
-- Store variables
self.variables = {}
local metadata = cjson.decode(encoded_metadata)
local multisite = false
local current_phase = ngx.get_phase()
for i, check_phase in ipairs({ "set", "access", "log", "preread" }) do
if current_phase == check_phase then
multisite = true
break
end
end
self.is_request = multisite
for k, v in pairs(metadata.settings) do
local value, err = utils.get_variable(k, v.context == "multisite" and multisite)
if value == nil then

View File

@ -12,6 +12,8 @@ local datastore = cdatastore:new()
local utils = {}
math.randomseed(os.time())
utils.get_variable = function(var, site_search)
-- Default site search to true
if site_search == nil then
@ -363,7 +365,6 @@ utils.get_rdns = function(ip)
for i, answer in ipairs(answers) do
if answer.ptrdname then
table.insert(ptrs, answer.ptrdname)
logger:log(ngx.ERR, answer.ptrdname)
end
end
end
@ -510,22 +511,74 @@ utils.get_deny_status = function()
return tonumber(status)
end
utils.check_session = function()
local _session, err, exists, refreshed = session.start({audience = "metadata"})
if exists then
for i, check in ipairs(ngx.ctx.bw.sessions_checks) do
local key = check[1]
local value = check[2]
if _session:get(key) ~= value then
local ok, err = _session:destroy()
if not ok then
_session:close()
return false, "session:destroy() error : " .. err
end
logger:log(ngx.WARN, "session check " .. key .. " failed, destroying session")
return utils.check_session()
end
end
else
for i, check in ipairs(ngx.ctx.bw.sessions_checks) do
_session:set(check[1], check[2])
end
local ok, err = _session:save()
if not ok then
_session:close()
return false, "session:save() error : " .. err
end
end
ngx.ctx.bw.sessions_is_checked = true
_session:close()
return true, exists
end
utils.get_session = function(audience)
-- Session already in context
if ngx.ctx.bw.session then
ngx.ctx.bw.session:set_audience(audience)
return ngx.ctx.bw.session
-- Check session
if not ngx.ctx.bw.sessions_is_checked then
local ok, err = utils.check_session()
if not ok then
return false, "error while checking session, " .. err
end
end
-- Open session and fill ctx
local _session, err, exists, refreshed = session.start({ audience = audience })
if err and err ~= "missing session cookie" and err ~= "no session" then
logger:log(ngx.ERR, "session:start() error : " .. err)
-- Open session with specific audience
local _session, err, exists = session.open({audience = audience})
if err then
logger:log(ngx.INFO, "session:open() error : " .. err)
end
_session:set_audience(audience)
ngx.ctx.bw.session = _session
return _session
end
utils.get_session_data = function(_session, site)
local site_only = site == nil or site
local data = _session:get_data()
if site_only then
return data[ngx.ctx.bw.server_name] or {}
end
return data
end
utils.set_session_data = function(_session, data, site)
local site_only = site == nil or site
if site_only then
local all_data = _session:get_data()
all_data[ngx.ctx.bw.server_name] = data
_session:set_data(all_data)
return _session:save()
end
_session:set_data(data)
return _session:save()
end
utils.is_banned = function(ip)
-- Check on local datastore
local reason, err = datastore:get("bans_ip_" .. ip)
@ -627,7 +680,7 @@ utils.new_cachestore = function()
use_redis = use_redis == "yes"
end
-- Instantiate
return require "bunkerweb.cachestore":new(use_redis)
return require "bunkerweb.cachestore":new(use_redis, true)
end
utils.regex_match = function(str, regex, options)
@ -672,4 +725,20 @@ utils.is_cosocket_available = function()
return false
end
utils.kill_all_threads = function(threads)
for i, thread in ipairs(threads) do
local ok, err = ngx.thread.kill(thread)
if not ok then
logger:log(ngx.ERR, "error while killing thread : " .. err)
end
end
end
utils.get_ctx_obj = function(obj)
if ngx.ctx and ngx.ctx.bw then
return ngx.ctx.bw[obj]
end
return nil
end
return utils

View File

@ -66,10 +66,6 @@ server {
local order, err = datastore:get("plugins_order")
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
local ok, err = lock:unlock()
if not ok then
logger:log(ngx.ERR, "lock:unlock() failed : " .. err)
end
return
end
order = cjson.decode(order)

View File

@ -49,6 +49,10 @@ lua_shared_dict cachestore {{ CACHESTORE_MEMORY_SIZE }};
lua_shared_dict cachestore_ipc {{ CACHESTORE_IPC_MEMORY_SIZE }};
lua_shared_dict cachestore_miss {{ CACHESTORE_MISS_MEMORY_SIZE }};
lua_shared_dict cachestore_locks {{ CACHESTORE_LOCKS_MEMORY_SIZE }};
# only show LUA socket errors at info/debug
{% if LOG_LEVEL != "info" and LOG_LEVEL != "debug" %}
lua_socket_log_errors off;
{% endif %}
# LUA init block
include /etc/nginx/init-lua.conf;

View File

@ -11,16 +11,9 @@ local logger = clogger:new("INIT")
local datastore = cdatastore:new()
logger:log(ngx.NOTICE, "init phase started")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new()
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Remove previous data from the datastore
logger:log(ngx.NOTICE, "deleting old keys from datastore ...")
local data_keys = {"^plugin_", "^variable_", "^plugins$", "^api_", "^misc_"}
local data_keys = {"^plugin", "^variable_", "^api_", "^misc_"}
for i, key in pairs(data_keys) do
local ok, err = datastore:delete_all(key)
if not ok then
@ -50,6 +43,13 @@ for line in io.lines("/etc/nginx/variables.env") do
end
logger:log(ngx.NOTICE, "saved variables into datastore")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new(false, true)
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Set API values into the datastore
logger:log(ngx.NOTICE, "saving API values into datastore ...")
local value, err = datastore:get("variable_USE_API")

View File

@ -1,158 +1,157 @@
init_by_lua_block {
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
local cdatastore = require "bunkerweb.datastore"
local cjson = require "cjson"
-- Start init phase
local logger = clogger:new("INIT-STREAM")
local datastore = cdatastore:new()
logger:log(ngx.NOTICE, "init-stream phase started")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new()
local ok, err = cachestore:purge()
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
local cdatastore = require "bunkerweb.datastore"
local cjson = require "cjson"
-- Start init phase
local logger = clogger:new("INIT")
local datastore = cdatastore:new()
logger:log(ngx.NOTICE, "init-stream phase started")
-- Remove previous data from the datastore
logger:log(ngx.NOTICE, "deleting old keys from datastore ...")
local data_keys = {"^plugin", "^variable_", "^api_", "^misc_"}
for i, key in pairs(data_keys) do
local ok, err = datastore:delete_all(key)
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Remove previous data from the datastore
logger:log(ngx.NOTICE, "deleting old keys from datastore ...")
local data_keys = {"^plugin_", "^variable_", "^plugins$", "^api_", "^misc_"}
for i, key in pairs(data_keys) do
local ok, err = datastore:delete_all(key)
if not ok then
logger:log(ngx.ERR, "can't delete " .. key .. " from datastore : " .. err)
return false
end
logger:log(ngx.INFO, "deleted " .. key .. " from datastore")
end
logger:log(ngx.NOTICE, "deleted old keys from datastore")
-- Load variables into the datastore
logger:log(ngx.NOTICE, "saving variables into datastore ...")
local file = io.open("/etc/nginx/variables.env")
if not file then
logger:log(ngx.ERR, "can't open /etc/nginx/variables.env file")
logger:log(ngx.ERR, "can't delete " .. key .. " from datastore : " .. err)
return false
end
file:close()
for line in io.lines("/etc/nginx/variables.env") do
local variable, value = line:match("^([^=]+)=(.*)$")
local ok, err = datastore:set("variable_" .. variable, value)
if not ok then
logger:log(ngx.ERR, "can't save variable " .. variable .. " into datastore : " .. err)
return false
end
logger:log(ngx.INFO, "saved variable " .. variable .. "=" .. value .. " into datastore")
logger:log(ngx.INFO, "deleted " .. key .. " from datastore")
end
logger:log(ngx.NOTICE, "deleted old keys from datastore")
-- Load variables into the datastore
logger:log(ngx.NOTICE, "saving variables into datastore ...")
local file = io.open("/etc/nginx/variables.env")
if not file then
logger:log(ngx.ERR, "can't open /etc/nginx/variables.env file")
return false
end
file:close()
for line in io.lines("/etc/nginx/variables.env") do
local variable, value = line:match("^([^=]+)=(.*)$")
local ok, err = datastore:set("variable_" .. variable, value)
if not ok then
logger:log(ngx.ERR, "can't save variable " .. variable .. " into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved variables into datastore")
-- Set API values into the datastore
logger:log(ngx.NOTICE, "saving API values into datastore ...")
local value, err = datastore:get("variable_USE_API")
logger:log(ngx.INFO, "saved variable " .. variable .. "=" .. value .. " into datastore")
end
logger:log(ngx.NOTICE, "saved variables into datastore")
-- Purge cache
local cachestore = require "bunkerweb.cachestore":new(false, true)
local ok, err = cachestore:purge()
if not ok then
logger:log(ngx.ERR, "can't purge cachestore : " .. err)
end
-- Set API values into the datastore
logger:log(ngx.NOTICE, "saving API values into datastore ...")
local value, err = datastore:get("variable_USE_API")
if not value then
logger:log(ngx.ERR, "can't get variable USE_API from the datastore : " .. err)
return false
end
if value == "yes" then
local value, err = datastore:get("variable_API_WHITELIST_IP")
if not value then
logger:log(ngx.ERR, "can't get variable USE_API from the datastore : " .. err)
logger:log(ngx.ERR, "can't get variable API_WHITELIST_IP from the datastore : " .. err)
return false
end
if value == "yes" then
local value, err = datastore:get("variable_API_WHITELIST_IP")
if not value then
logger:log(ngx.ERR, "can't get variable API_WHITELIST_IP from the datastore : " .. err)
return false
end
local whitelists = {}
for whitelist in value:gmatch("%S+") do
table.insert(whitelists, whitelist)
end
local ok, err = datastore:set("api_whitelist_ip", cjson.encode(whitelists))
local whitelists = {}
for whitelist in value:gmatch("%S+") do
table.insert(whitelists, whitelist)
end
local ok, err = datastore:set("api_whitelist_ip", cjson.encode(whitelists))
if not ok then
logger:log(ngx.ERR, "can't save API whitelist_ip to datastore : " .. err)
return false
end
logger:log(ngx.INFO, "saved API whitelist_ip into datastore")
end
logger:log(ngx.NOTICE, "saved API values into datastore")
-- Load plugins into the datastore
logger:log(ngx.NOTICE, "saving plugins into datastore ...")
local plugins = {}
local plugin_paths = {"/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins"}
for i, plugin_path in ipairs(plugin_paths) do
local paths = io.popen("find -L " .. plugin_path .. " -maxdepth 1 -type d ! -path " .. plugin_path)
for path in paths:lines() do
local ok, plugin = helpers.load_plugin(path .. "/plugin.json")
if not ok then
logger:log(ngx.ERR, "can't save API whitelist_ip to datastore : " .. err)
return false
end
logger:log(ngx.INFO, "saved API whitelist_ip into datastore")
end
logger:log(ngx.NOTICE, "saved API values into datastore")
-- Load plugins into the datastore
logger:log(ngx.NOTICE, "saving plugins into datastore ...")
local plugins = {}
local plugin_paths = {"/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins"}
for i, plugin_path in ipairs(plugin_paths) do
local paths = io.popen("find -L " .. plugin_path .. " -maxdepth 1 -type d ! -path " .. plugin_path)
for path in paths:lines() do
local ok, plugin = helpers.load_plugin(path .. "/plugin.json")
if not ok then
logger:log(ngx.ERR, plugin)
else
local ok, err = datastore:set("plugin_" .. plugin.id, cjson.encode(plugin))
if not ok then
logger:log(ngx.ERR, "can't save " .. plugin.id .. " into datastore : " .. err)
else
table.insert(plugins, plugin)
logger:log(ngx.NOTICE, "loaded plugin " .. plugin.id .. " v" .. plugin.version)
end
end
end
end
local ok, err = datastore:set("plugins", cjson.encode(plugins))
if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saving plugins order into datastore ...")
local ok, order = helpers.order_plugins(plugins)
if not ok then
logger:log(ngx.ERR, "can't compute plugins order : " .. err)
return false
end
for phase, id_list in pairs(order) do
logger:log(ngx.NOTICE, "plugins order for phase " .. phase .. " : " .. cjson.encode(id_list))
end
local ok, err = datastore:set("plugins_order", cjson.encode(order))
if not ok then
logger:log(ngx.ERR, "can't save plugins order into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved plugins order into datastore")
-- Call init() method
logger:log(ngx.NOTICE, "calling init() methods of plugins ...")
for i, plugin_id in ipairs(order["init"]) 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.NOTICE, err)
logger:log(ngx.ERR, plugin)
else
-- Check if plugin has init method
if plugin_lua.init ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
local ok, ret = helpers.call_plugin(plugin_obj, "init")
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin_id .. ":init() call failed : " .. ret.msg)
else
logger:log(ngx.NOTICE, plugin_id .. ":init() call successful : " .. ret.msg)
end
end
local ok, err = datastore:set("plugin_" .. plugin.id, cjson.encode(plugin))
if not ok then
logger:log(ngx.ERR, "can't save " .. plugin.id .. " into datastore : " .. err)
else
logger:log(ngx.NOTICE, "skipped execution of " .. plugin.id .. " because method init() is not defined")
table.insert(plugins, plugin)
logger:log(ngx.NOTICE, "loaded plugin " .. plugin.id .. " v" .. plugin.version)
end
end
end
logger:log(ngx.NOTICE, "called init() methods of plugins")
logger:log(ngx.NOTICE, "init-stream phase ended")
}
end
local ok, err = datastore:set("plugins", cjson.encode(plugins))
if not ok then
logger:log(ngx.ERR, "can't save plugins into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saving plugins order into datastore ...")
local ok, order = helpers.order_plugins(plugins)
if not ok then
logger:log(ngx.ERR, "can't compute plugins order : " .. err)
return false
end
for phase, id_list in pairs(order) do
logger:log(ngx.NOTICE, "plugins order for phase " .. phase .. " : " .. cjson.encode(id_list))
end
local ok, err = datastore:set("plugins_order", cjson.encode(order))
if not ok then
logger:log(ngx.ERR, "can't save plugins order into datastore : " .. err)
return false
end
logger:log(ngx.NOTICE, "saved plugins order into datastore")
-- Call init() method
logger:log(ngx.NOTICE, "calling init() methods of plugins ...")
for i, plugin_id in ipairs(order["init"]) 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.NOTICE, err)
else
-- Check if plugin has init method
if plugin_lua.init ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)
if not ok then
logger:log(ngx.ERR, plugin_obj)
else
local ok, ret = helpers.call_plugin(plugin_obj, "init")
if not ok then
logger:log(ngx.ERR, ret)
elseif not ret.ret then
logger:log(ngx.ERR, plugin_id .. ":init() call failed : " .. ret.msg)
else
logger:log(ngx.NOTICE, plugin_id .. ":init() call successful : " .. ret.msg)
end
end
else
logger:log(ngx.NOTICE, "skipped execution of " .. plugin.id .. " because method init() is not defined")
end
end
end
logger:log(ngx.NOTICE, "called init() methods of plugins")
logger:log(ngx.NOTICE, "init-stream phase ended")
}

View File

@ -23,7 +23,7 @@ local ready_work = function(premature)
end
-- Instantiate lock
local lock = require "resty.lock":new("worker_lock")
local lock = require "resty.lock":new("worker_lock", {timeout = 10})
if not lock then
logger:log(ngx.ERR, "lock:new() failed : " .. err)
return

View File

@ -46,10 +46,6 @@ end
local order, err = datastore:get("plugins_order")
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
local ok, err = lock:unlock()
if not ok then
logger:log(ngx.ERR, "lock:unlock() failed : " .. err)
end
return
end
order = cjson.decode(order)

View File

@ -27,10 +27,6 @@ logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
local order, err = datastore:get("plugins_order")
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
local ok, err = lock:unlock()
if not ok then
logger:log(ngx.ERR, "lock:unlock() failed : " .. err)
end
return
end
order = cjson.decode(order)

View File

@ -27,10 +27,6 @@ logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
local order, err = datastore:get("plugins_order")
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
local ok, err = lock:unlock()
if not ok then
logger:log(ngx.ERR, "lock:unlock() failed : " .. err)
end
return
end
order = cjson.decode(order)

View File

@ -42,10 +42,6 @@ logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
local order, err = datastore:get("plugins_order")
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
local ok, err = lock:unlock()
if not ok then
logger:log(ngx.ERR, "lock:unlock() failed : " .. err)
end
return
end
order = cjson.decode(order)

View File

@ -27,10 +27,6 @@ logger:log(ngx.INFO, "ngx.ctx filled (ret = " .. ret .. ")")
local order, err = datastore:get("plugins_order")
if not order then
logger:log(ngx.ERR, "can't get plugins order from datastore : " .. err)
local ok, err = lock:unlock()
if not ok then
logger:log(ngx.ERR, "lock:unlock() failed : " .. err)
end
return
end
order = cjson.decode(order)
@ -45,7 +41,7 @@ for i, plugin_id in ipairs(order.log_stream) do
elseif plugin_lua == nil then
logger:log(ngx.INFO, err)
else
-- Check if plugin has log method
-- Check if plugin has log_stream method
if plugin_lua.log_stream ~= nil then
-- New call
local ok, plugin_obj = helpers.new_plugin(plugin_lua)

View File

@ -77,7 +77,7 @@ for i, plugin_id in ipairs(order.preread) do
if ret.status then
if ret.status == utils.get_deny_status() then
ngx.ctx.reason = plugin_id
logger:log(ngx.WARN, "denied access from " .. plugin_id .. " : " .. ret.msg)
logger:log(ngx.WARN, "denied preread from " .. plugin_id .. " : " .. ret.msg)
else
logger:log(ngx.NOTICE, plugin_id .. " returned status " .. tostring(ret.status) .. " : " .. ret.msg)
end

View File

@ -33,6 +33,10 @@ lua_shared_dict cachestore_stream {{ CACHESTORE_MEMORY_SIZE }};
lua_shared_dict cachestore_ipc_stream {{ CACHESTORE_IPC_MEMORY_SIZE }};
lua_shared_dict cachestore_miss_stream {{ CACHESTORE_MISS_MEMORY_SIZE }};
lua_shared_dict cachestore_locks_stream {{ CACHESTORE_LOCKS_MEMORY_SIZE }};
# only show LUA socket errors at info/debug
{% if LOG_LEVEL != "info" and LOG_LEVEL != "debug" %}
lua_socket_log_errors off;
{% endif %}
# LUA init block
include /etc/nginx/init-stream-lua.conf;

View File

@ -26,9 +26,15 @@ function antibot:access()
return self:ret(true, "antibot not activated")
end
-- Get session and data
self.session = utils.get_session("antibot")
self:get_session_data()
-- Get session data
local session, err = utils.get_session("antibot")
if not session then
return self:ret(false, "can't get session : " .. err, ngx.HTTP_INTERNAL_SERVER_ERROR)
end
self.session = session
self.session_data = utils.get_session_data(self.session)
-- Check if session is valid
self:check_session()
-- Don't go further if client resolved the challenge
if self.session_data.resolved then
@ -50,6 +56,11 @@ function antibot:access()
return self:ret(true, "redirecting client to the challenge uri", nil, self.variables["ANTIBOT_URI"])
end
-- Cookie case : don't display challenge page
if self.session_data.resolved then
return self:ret(true, "client already resolved the challenge", nil, self.session_data.original_uri)
end
-- Display challenge needed
if ngx.ctx.bw.request_method == "GET" then
ngx.ctx.bw.antibot_display_content = true
@ -89,13 +100,25 @@ function antibot:content()
if self.variables["USE_ANTIBOT"] == "no" then
return self:ret(true, "antibot not activated")
end
-- Check if display content is needed
if not ngx.ctx.bw.antibot_display_content then
return self:ret(true, "display content not needed", nil, "/")
end
-- Get session and data
self.session = utils.get_session("antibot")
self:get_session_data(true)
-- Get session data
local session, err = utils.get_session("antibot")
if not session then
return self:ret(false, "can't get session : " .. err, ngx.HTTP_INTERNAL_SERVER_ERROR)
end
self.session = session
self.session_data = utils.get_session_data(self.session)
-- Direct access without session
if not self.session_data.prepared then
return self:ret(true, "no session", nil, "/")
end
-- Display content
local ok, err = self:display_challenge()
if not ok then
@ -104,50 +127,42 @@ function antibot:content()
return self:ret(true, "content displayed")
end
function antibot:get_session_data(no_check)
local session_data = self.session:get_data()
if session_data[ngx.ctx.bw.server_name] then
local data = cjson.decode(session_data[ngx.ctx.bw.server_name])
if no_check then
self.session_data = data
return
end
if not data.time_resolve and not data.time_valid then
self.session_data = {}
self.session_updated = true
return
end
local time = ngx.now()
self.session_data = data
-- Check valid time
if data.resolved and (data.time_valid > time or time - data.time_valid > tonumber(self.variables["ANTIBOT_TIME_VALID"])) then
self.session_data.resolved = false
self.session_data.prepared = false
self.session_updated = true
return
end
-- Check resolve time
if not data.resolved and (data.time_resolve > time or time - data.time_resolve > tonumber(self.variables["ANTIBOT_TIME_RESOLVE"])) then
self.session_data.prepared = false
self.session_updated = true
return
end
-- Session is valid
function antibot:check_session()
-- Get values
local time_resolve = self.session_data.time_resolve
local time_valid = self.session_data.time_valid
-- Not resolved and not prepared
if not time_resolve and not time_valid then
self.session_data = {}
self.session_updated = true
return
end
-- Check if still valid
local time = ngx.now()
local resolved = self.session_data.resolved
if resolved and (time_valid > time or time - time_valid > tonumber(self.variables["ANTIBOT_TIME_VALID"])) then
self.session_data = {}
self.session_updated = true
return
end
-- Check if new prepare is needed
if not resolved and (time_resolve > time or time - time_resolve > tonumber(self.variables["ANTIBOT_TIME_RESOLVE"])) then
self.session_data = {}
self.session_updated = true
return
end
self.session_data = {}
self.session_updated = true
return
end
function antibot:set_session_data()
if self.session_updated then
local session_data = self.session:get_data()
session_data[ngx.ctx.bw.server_name] = cjson.encode(self.session_data)
self.session:set_data(session_data)
return self.session:save()
local ok, err = utils.set_session_data(self.session, self.session_data)
if not ok then
return false, err
end
self.session_updated = false
return true, "updated"
end
return true, "no updates"
return true, "no update"
end
function antibot:prepare_challenge()

View File

@ -7,12 +7,6 @@ local badbehavior = class("badbehavior", plugin)
function badbehavior:initialize()
-- Call parent initialize
plugin.initialize(self, "badbehavior")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
end
function badbehavior:log()
@ -146,7 +140,7 @@ end
function badbehavior.redis_increase(ip, count_time, ban_time)
-- Instantiate objects
local clusterstore = require "bunkerweb.clusterstore":new()
local clusterstore = require "bunkerweb.clusterstore":new(false)
-- Our LUA script to execute on redis
local redis_script = [[
local ret_incr = redis.pcall("INCR", KEYS[1])
@ -188,7 +182,7 @@ end
function badbehavior.redis_decrease(ip, count_time)
-- Instantiate objects
local clusterstore = require "bunkerweb.clusterstore":new()
local clusterstore = require "bunkerweb.clusterstore":new(false)
-- Our LUA script to execute on redis
local redis_script = [[
local ret_decr = redis.pcall("DECR", KEYS[1])

View File

@ -11,12 +11,6 @@ local blacklist = class("blacklist", plugin)
function blacklist:initialize()
-- Call parent initialize
plugin.initialize(self, "blacklist")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_blacklist_lists")
@ -47,8 +41,6 @@ function blacklist:initialize()
end
end
end
-- Instantiate cachestore
self.cachestore = cachestore:new(self.use_redis)
end
function blacklist:is_needed()
@ -267,20 +259,21 @@ function blacklist:is_blacklisted_ip()
if ngx.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(ngx.ctx.bw.remote_addr)
if not asn then
return nil, "ASN " .. err
end
local ignore = false
for i, ignore_asn in ipairs(self.lists["IGNORE_ASN"]) do
if ignore_asn == tostring(asn) then
ignore = true
break
self.logger:log(ngx.ERR, "can't get ASN of IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
else
local ignore = false
for i, ignore_asn in ipairs(self.lists["IGNORE_ASN"]) do
if ignore_asn == tostring(asn) then
ignore = true
break
end
end
end
-- Check if ASN is in blacklist
if not ignore then
for i, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
-- Check if ASN is in blacklist
if not ignore then
for i, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
end
end
end
end

View File

@ -38,7 +38,7 @@
"help": "Value of the Cache-Control HTTP header.",
"id": "client-cache-control",
"label": "Cache-Control header",
"regex": "^(?!(, ?| ))((, )?(((max-age|s-maxage|stale-while-revalidate|stale-if-error)=[0-9]+(?!.*6))|((?!.*public)private|(?!.*private)public)|(must|proxy)-revalidate|must-understand|immutable|no-(cache|store|transform)))+$",
"regex": "^(?!(, ?| ))((, )?(((max-age|s-maxage|stale-while-revalidate|stale-if-error)=\\d+(?!.*\\6))|((?!.*public)private|(?!.*private)public)|(must|proxy)-revalidate|must-understand|immutable|no-(cache|store|transform))(?!.*\\4))+$",
"type": "text"
}
}

View File

@ -9,13 +9,6 @@ local country = class("country", plugin)
function country:initialize()
-- Call parent initialize
plugin.initialize(self, "country")
-- Instantiate cachestore
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
self.cachestore = cachestore:new(self.use_redis)
end
function country:access()

View File

@ -10,13 +10,6 @@ local dnsbl = class("dnsbl", plugin)
function dnsbl:initialize()
-- Call parent initialize
plugin.initialize(self, "dnsbl")
-- Instantiate cachestore
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
self.cachestore = cachestore:new(self.use_redis)
end
function dnsbl:init_worker()
@ -32,9 +25,18 @@ function dnsbl:init_worker()
return self:ret(true, "no service uses DNSBL, skipping init_worker")
end
-- Loop on DNSBL list
local threads = {}
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
local result, err = self:is_in_dnsbl("127.0.0.2", server)
if result == nil then
-- Create thread
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, "127.0.0.2", server)
threads[server] = thread
end
-- Wait for threads
for dnsbl, thread in pairs(threads) do
local ok, result, server, err = ngx.thread.wait(thread)
if not ok then
self.logger:log(ngx.ERR, "error while waiting thread of " .. dnsbl .. " check : " .. result)
elseif result == nil then
self.logger:log(ngx.ERR, "error while sending DNS request to " .. server .. " : " .. err)
elseif not result then
self.logger:log(ngx.ERR, "dnsbl check for " .. server .. " failed")
@ -69,25 +71,74 @@ function dnsbl:access()
utils.get_deny_status())
end
-- Loop on DNSBL list
local threads = {}
for server in self.variables["DNSBL_LIST"]:gmatch("%S+") do
local result, err = self:is_in_dnsbl(ngx.ctx.bw.remote_addr, server)
-- Create thread
local thread = ngx.thread.spawn(self.is_in_dnsbl, self, ngx.ctx.bw.remote_addr, server)
threads[server] = thread
end
-- Wait for threads
local ret_threads = nil
local ret_err = nil
local ret_server = nil
while true do
-- Compute threads to wait
local wait_threads = {}
for dnsbl, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
-- No server reported IP
if #wait_threads == 0 then
break
end
-- Wait for first thread
local ok, result, server, err = ngx.thread.wait(unpack(wait_threads))
-- Error case
if not ok then
ret_threads = false
ret_err = "error while waiting thread : " .. result
break
end
-- Remove thread from list
threads[server] = nil
-- DNS error
if result == nil then
self.logger:log(ngx.ERR, "error while sending DNS request to " .. server .. " : " .. err)
end
-- IP is in DNSBL
if result then
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, server)
ret_threads = true
ret_err = "IP is blacklisted by " .. server
ret_server = server
break
end
end
if ret_threads ~= nil then
-- Kill other threads
if #threads > 0 then
local wait_threads = {}
for dnsbl, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
end
-- Blacklisted by a server : add to cache and deny access
if ret_threads then
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, ret_server)
if not ok then
return self:ret(false, "error while adding element to cache : " .. err)
end
return self:ret(true, "IP is blacklisted by " .. server, utils.get_deny_status())
return self:ret(true, "IP is blacklisted by " .. ret_server, utils.get_deny_status())
end
-- Error case
return self:ret(false, ret_err)
end
-- IP is not in DNSBL
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr, "ok")
if not ok then
return self:ret(false, "IP is not in DNSBL (error = " .. err .. ")")
end
return self:ret(true, "IP is not in DNSBL", false, nil)
return self:ret(true, "IP is not in DNSBL")
end
function dnsbl:preread()
@ -114,14 +165,14 @@ function dnsbl:is_in_dnsbl(ip, server)
local request = resolver.arpa_str(ip):gsub("%.in%-addr%.arpa", ""):gsub("%.ip6%.arpa", "") .. "." .. server
local ips, err = utils.get_ips(request, false)
if not ips then
return nil, err
return nil, server, err
end
for i, ip in ipairs(ips) do
if ip:find("^127%.0%.0%.") then
return true, "success"
return true, server
end
end
return false, "success"
return false, server
end
return dnsbl

View File

@ -10,12 +10,6 @@ local greylist = class("greylist", plugin)
function greylist:initialize()
-- Call parent initialize
plugin.initialize(self, "greylist")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_greylist_lists")
@ -41,8 +35,6 @@ function greylist:initialize()
end
end
end
-- Instantiate cachestore
self.cachestore = cachestore:new(self.use_redis)
end
function greylist:is_needed()
@ -216,11 +208,12 @@ function greylist:is_greylisted_ip()
if ngx.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(ngx.ctx.bw.remote_addr)
if not asn then
return nil, "ASN " .. err
end
for i, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
self.logger:log(ngx.ERR, "can't get ASN of IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
else
for i, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
end
end
end
end

View File

@ -10,13 +10,6 @@ local limit = class("limit", plugin)
function limit:initialize()
-- Call parent initialize
plugin.initialize(self, "limit")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
self.clusterstore = clusterstore:new()
-- Load rules if needed
if ngx.get_phase() ~= "init" and self:is_needed() then
-- Get all rules from datastore

View File

@ -9,7 +9,6 @@ local redis = class("redis", plugin)
function redis:initialize()
-- Call parent initialize
plugin.initialize(self, "redis")
self.clusterstore = clusterstore:new()
end
function redis:init_worker()

View File

@ -2,19 +2,13 @@ local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local reversescan = class("reversescan", plugin)
function reversescan:initialize()
-- Call parent initialize
plugin.initialize(self, "reversescan")
-- Instantiate cachestore
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
self.cachestore = cachestore:new(self.use_redis)
end
function reversescan:access()
@ -23,31 +17,103 @@ function reversescan:access()
return self:ret(true, "reverse scan not activated")
end
-- Loop on ports
local threads = {}
local ret_threads = nil
local ret_err = nil
for port in self.variables["REVERSE_SCAN_PORTS"]:gmatch("%S+") do
-- Check if the scan is already cached
local ok, cached = self:is_in_cache(ngx.ctx.bw.remote_addr .. ":" .. port)
if not ok then
return self:ret(false, "error getting cache from datastore : " .. cached)
end
if cached == "open" then
return self:ret(true, "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr,
utils.get_deny_status())
ret_threads = false
ret_err = "error getting info from cachestore : " .. cached
break
-- Deny access if port opened
elseif cached == "open" then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr
break
-- Perform scan in a thread
elseif not cached then
-- Do the scan
local res = self:scan(ngx.ctx.bw.remote_addr, tonumber(port),
tonumber(self.variables["REVERSE_SCAN_TIMEOUT"]))
-- Cache the result
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr .. ":" .. port, res)
if not ok then
return self:ret(false, "error updating cache from datastore : " .. err)
end
-- Deny request if port is open
if res == "open" then
return self:ret(true, "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr,
utils.get_deny_status())
end
local thread = ngx.thread.spawn(self.scan, ngx.ctx.bw.remote_addr, tonumber(port), tonumber(self.variables["REVERSE_SCAN_TIMEOUT"]))
threads[port] = thread
end
end
if ret_threads ~= nil then
if #threads > 0 then
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
end
-- Open port case
if ret_threads then
return self:ret(true, ret_err, utils.get_deny_status())
end
-- Error case
return self:ret(false, ret_err)
end
-- Check results of threads
ret_threads = nil
ret_err = nil
local results = {}
while true do
-- Compute threads to wait
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
-- No port opened
if #wait_threads == 0 then
break
end
-- Wait for first thread
local ok, open, port = ngx.thread.wait(unpack(wait_threads))
-- Error case
if not ok then
ret_threads = false
ret_err = "error while waiting thread : " .. open
break
end
port = tostring(port)
-- Remove thread from list
threads[port] = nil
-- Add result to cache
local result = "close"
if open then
result = "open"
end
results[port] = result
-- Port is opened
if open then
ret_threads = true
ret_err = "port " .. port .. " is opened for IP " .. ngx.ctx.bw.remote_addr
break
end
end
-- Kill running threads
if #threads > 0 then
local wait_threads = {}
for port, thread in pairs(threads) do
table.insert(wait_threads, thread)
end
utils.kill_all_threads(wait_threads)
end
-- Cache results
for port, result in pairs(results) do
local ok, err = self:add_to_cache(ngx.ctx.bw.remote_addr .. ":" .. port, result)
if not ok then
return self:ret(false, "error while adding element to cache : " .. err)
end
end
if ret_threads ~= nil then
-- Open port case
if ret_threads then
return self:ret(true, ret_err, utils.get_deny_status())
end
-- Error case
return self:ret(false, ret_err)
end
-- No port opened
return self:ret(true, "no port open for IP " .. ngx.ctx.bw.remote_addr)
end
@ -56,15 +122,15 @@ function reversescan:preread()
return self:access()
end
function reversescan:scan(ip, port, timeout)
function reversescan.scan(ip, port, timeout)
local tcpsock = ngx.socket.tcp()
tcpsock:settimeout(timeout)
local ok, err = tcpsock:connect(ip, port)
tcpsock:close()
if not ok then
return "close"
return false, port
end
return "open"
return true, port
end
function reversescan:is_in_cache(ip_port)

View File

@ -46,9 +46,27 @@
"default": "86400",
"help": "Maximum time (in seconds) before a session is destroyed.",
"id": "sessions-absolute-timeout",
"label": "SessionS absolute timeout",
"label": "Sessions absolute timeout",
"regex": "^\\d+$",
"type": "text"
},
"SESSIONS_CHECK_IP": {
"context": "global",
"default": "yes",
"help": "Destroy session if IP address is different than original one.",
"id": "sessions-check-ip",
"label": "Sessions check IP",
"regex": "^(yes|no)$",
"type": "check"
},
"SESSIONS_CHECK_USER_AGENT": {
"context": "global",
"default": "yes",
"help": "Destroy session if User-Agent is different than original one.",
"id": "sessions-user-agent",
"label": "Sessions check User-Agent",
"regex": "^(yes|no)$",
"type": "check"
}
}
}

View File

@ -8,6 +8,37 @@ local sessions = class("sessions", plugin)
function sessions:initialize()
-- Call parent initialize
plugin.initialize(self, "sessions")
-- Check if random cookie name and secrets are already generated
local is_random = {
"SESSIONS_SECRET",
"SESSIONS_NAME"
}
self.randoms = {}
for i, var in ipairs(is_random) do
if self.variables[var] == "random" then
local data, err = self.datastore:get("storage_sessions_" .. var)
if data then
self.randoms[var] = data
end
end
end
end
function sessions:set()
if self.is_loading or self.kind ~= "http" then
return self:ret(true, "set not needed")
end
local checks = {
["IP"] = ngx.ctx.bw.remote_addr,
["USER_AGENT"] = ngx.ctx.bw.http_user_agent or ""
}
ngx.ctx.bw.sessions_checks = {}
for check, value in pairs(checks) do
if self.variables["SESSIONS_CHECK_" .. check] == "yes" then
table.insert(ngx.ctx.bw.sessions_checks, {check, value})
end
end
return self:ret(true, "success")
end
function sessions:init()
@ -41,10 +72,26 @@ function sessions:init()
absolute_timeout = tonumber(self.variables["SESSIONS_ABSOLUTE_TIMEOUT"])
}
if self.variables["SESSIONS_SECRET"] == "random" then
config.secret = utils.rand(16)
if self.randoms["SESSIONS_SECRET"] then
config.secret = self.randoms["SESSIONS_SECRET"]
else
config.secret = utils.rand(16)
local ok, err = self.datastore:set("storage_sessions_SESSIONS_SECRET", config.secret)
if not ok then
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
end
end
end
if self.variables["SESSIONS_NAME"] == "random" then
config.cookie_name = utils.rand(16)
if self.randoms["SESSIONS_NAME"] then
config.cookie_name = self.randoms["SESSIONS_NAME"]
else
config.cookie_name = utils.rand(16)
local ok, err = self.datastore:set("storage_sessions_SESSIONS_NAME", config.cookie_name)
if not ok then
self.logger:log(ngx.ERR, "error from datastore:set : " .. err)
end
end
end
if redis_vars["USE_REDIS"] ~= "yes" then
config.storage = "cookie"
@ -56,7 +103,7 @@ function sessions:init()
send_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
read_timeout = tonumber(redis_vars["REDIS_TIMEOUT"]),
keepalive_timeout = tonumber(redis_vars["REDIS_KEEPALIVE_IDLE"]),
pool = "bw",
pool = "bw-redis",
pool_size = tonumber(redis_vars["REDIS_KEEPALIVE_POOL"]),
ssl = redis_vars["REDIS_SSL"] == "yes",
host = redis_vars["REDIS_HOST"],

View File

@ -12,12 +12,6 @@ local whitelist = class("whitelist", plugin)
function whitelist:initialize()
-- Call parent initialize
plugin.initialize(self, "whitelist")
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
self.logger:log(ngx.ERR, err)
end
self.use_redis = use_redis == "yes"
-- Decode lists
if ngx.get_phase() ~= "init" and self:is_needed() then
local lists, err = self.datastore:get("plugin_whitelist_lists")
@ -43,8 +37,6 @@ function whitelist:initialize()
end
end
end
-- Instantiate cachestore
self.cachestore = cachestore:new(self.use_redis)
end
function whitelist:is_needed()
@ -271,7 +263,6 @@ function whitelist:is_whitelisted_ip()
end
end
if forward_check then
local forward_ok = false
local ip_list, err = utils.get_ips(forward_check)
if ip_list then
for i, ip in ipairs(ip_list) do
@ -293,11 +284,12 @@ function whitelist:is_whitelisted_ip()
if ngx.ctx.bw.ip_is_global then
local asn, err = utils.get_asn(ngx.ctx.bw.remote_addr)
if not asn then
return nil, "ASN " .. err
end
for i, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
self.logger:log(ngx.ERR, "can't get ASN of IP " .. ngx.ctx.bw.remote_addr .. " : " .. err)
else
for i, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
end
end
end
end

View File

@ -265,13 +265,13 @@ if [ "$dopatch" = "yes" ] ; then
do_and_check_cmd cp deps/misc/lua-pack.Makefile deps/src/lua-pack/Makefile
fi
# lua-resty-openssl v0.8.21
# lua-resty-openssl v0.8.22
echo " Downloading lua-resty-openssl"
dopatch="no"
if [ ! -d "deps/src/lua-resty-openssl" ] ; then
dopatch="yes"
fi
git_secure_clone "https://github.com/fffonion/lua-resty-openssl.git" "15bc59b97feb5acf25fbdd9426cf73870cf7c838"
git_secure_clone "https://github.com/fffonion/lua-resty-openssl.git" "484907935e60273d31626ac849b23a2d218173de"
if [ "$dopatch" == "yes" ] ; then
do_and_check_cmd rm -r deps/src/lua-resty-openssl/t
fi
@ -287,6 +287,10 @@ if [ "$dopatch" = "yes" ] ; then
do_and_check_cmd patch deps/src/lua-ffi-zlib/lib/ffi-zlib.lua deps/misc/lua-ffi-zlib.patch
fi
# lua-resty-signal v0.03
echo " Downloading lua-resty-signal"
git_secure_clone "https://github.com/openresty/lua-resty-signal.git" "d07163e8cfa673900e66048cd2a1f18523aecf16"
# ModSecurity v3.0.9
echo " Downloading ModSecurity"
dopatch="no"

View File

@ -154,6 +154,11 @@ do_and_check_cmd cp /tmp/bunkerweb/deps/src/lua-resty-openssl/lib/resty/openssl.
echo " Installing lua-ffi-zlib"
do_and_check_cmd cp /tmp/bunkerweb/deps/src/lua-ffi-zlib/lib/ffi-zlib.lua /usr/share/bunkerweb/deps/lib/lua
# Installing lua-resty-signal
echo " Installing lua-resty-signal"
CHANGE_DIR="/tmp/bunkerweb/deps/src/lua-resty-signal" do_and_check_cmd make PREFIX=/usr/share/bunkerweb/deps -j $NTASK
CHANGE_DIR="/tmp/bunkerweb/deps/src/lua-resty-signal" do_and_check_cmd make PREFIX=/usr/share/bunkerweb/deps LUA_LIB_DIR=/usr/share/bunkerweb/deps/lib/lua install
# Compile dynamic modules
echo " Compiling and installing dynamic modules"
CONFARGS="$(nginx -V 2>&1 | sed -n -e 's/^.*arguments: //p')"

View File

@ -2,6 +2,12 @@
## [Unreleased]
<a name="0.8.22"></a>
## [0.8.22] - 2023-04-26
### bug fixes
- **crypto:** use OPENSSL_free in BoringSSL ([#107](https://github.com/fffonion/lua-resty-openssl/issues/107)) [7830212](https://github.com/fffonion/lua-resty-openssl/commit/78302123ac744f2d0b6de1156e459e9ea72b7edb)
<a name="0.8.21"></a>
## [0.8.21] - 2023-03-24
### features
@ -491,7 +497,8 @@
- **x509:** export pubkey [ede4f81](https://github.com/fffonion/lua-resty-openssl/commit/ede4f817cb0fe092ad6f9ab5d6ecdcde864a9fd8)
[Unreleased]: https://github.com/fffonion/lua-resty-openssl/compare/0.8.21...HEAD
[Unreleased]: https://github.com/fffonion/lua-resty-openssl/compare/0.8.22...HEAD
[0.8.22]: https://github.com/fffonion/lua-resty-openssl/compare/0.8.21...0.8.22
[0.8.21]: https://github.com/fffonion/lua-resty-openssl/compare/0.8.20...0.8.21
[0.8.20]: https://github.com/fffonion/lua-resty-openssl/compare/0.8.19...0.8.20
[0.8.19]: https://github.com/fffonion/lua-resty-openssl/compare/0.8.18...0.8.19

View File

@ -11,6 +11,7 @@ all: ;
install: all
cp -rpv lib/resty/openssl/. $(DESTDIR)$(LUA_LIB_DIR)/resty/openssl
cp -pv lib/resty/openssl.lua $(DESTDIR)$(LUA_LIB_DIR)/resty/
test: all
PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t

View File

@ -25,7 +25,7 @@ try_require_modules()
local _M = {
_VERSION = '0.8.21',
_VERSION = '0.8.22',
}
local libcrypto_name

View File

@ -3,6 +3,7 @@ local C = ffi.C
local OPENSSL_10 = require("resty.openssl.version").OPENSSL_10
local OPENSSL_11_OR_LATER = require("resty.openssl.version").OPENSSL_11_OR_LATER
local BORINGSSL = require("resty.openssl.version").BORINGSSL
local OPENSSL_free
if OPENSSL_10 then
@ -10,6 +11,11 @@ if OPENSSL_10 then
void CRYPTO_free(void *ptr);
]]
OPENSSL_free = C.CRYPTO_free
elseif BORINGSSL then
ffi.cdef [[
void OPENSSL_free(void *ptr);
]]
OPENSSL_free = C.OPENSSL_free
elseif OPENSSL_11_OR_LATER then
ffi.cdef [[
void CRYPTO_free(void *ptr, const char *file, int line);

View File

@ -1,8 +1,8 @@
package = "lua-resty-openssl"
version = "0.8.21-1"
version = "0.8.22-1"
source = {
url = "git+https://github.com/fffonion/lua-resty-openssl.git",
tag = "0.8.21"
tag = "0.8.22"
}
description = {
detailed = "FFI-based OpenSSL binding for LuaJIT.",

View File

@ -0,0 +1,9 @@
*~
*.swp
*.swo
t/servroot*
/go
/reindex
/a.lua
*.o
*.so

View File

@ -0,0 +1,46 @@
sudo: required
dist: xenial
os: linux
language: c
compiler:
- gcc
- clang
env:
global:
- JOBS=3
- NGX_BUILD_JOBS=$JOBS
- LUAJIT_PREFIX=/opt/luajit21
- LUAJIT_LIB=$LUAJIT_PREFIX/lib
- LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1
- LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH
matrix:
- NGINX_VERSION=1.19.3
install:
- sudo apt-get install -qq -y cpanminus axel
- sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1)
- git clone https://github.com/openresty/openresty.git ../openresty
- git clone https://github.com/openresty/nginx-devel-utils.git
- git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module
- git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module
- git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core
- git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache
- git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx
- git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git
script:
- make
- cd luajit2/
- make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1)
- sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1)
- cd ..
- export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH
- export NGX_BUILD_CC=$CC
- ngx-build $NGINX_VERSION --with-http_realip_module --add-module=../ndk-nginx-module --add-module=../lua-nginx-module --with-debug > build.log 2>&1 || (cat build.log && exit 1)
- nginx -V
- ldd `which nginx`|grep -E 'luajit|ssl|pcre'
- prove -r t

View File

@ -0,0 +1,46 @@
OPENRESTY_PREFIX=/usr/local/openresty
PREFIX ?= /usr/local
LUA_INCLUDE_DIR ?= $(PREFIX)/include
LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION)
INSTALL ?= install
.PHONY: all test install
SRC := resty_signal.c
OBJ := $(SRC:.c=.o)
C_SO_NAME := librestysignal.so
CFLAGS := -O3 -g -Wall -fpic
LDFLAGS := -shared
# on Mac OS X, one should set instead:
# LDFLAGS := -bundle -undefined dynamic_lookup
MY_CFLAGS := $(CFLAGS)
MY_LDFLAGS := $(LDFLAGS) -fvisibility=hidden
test := t
.PHONY = all test clean install
all : $(C_SO_NAME)
${OBJ} : %.o : %.c
$(CC) $(MY_CFLAGS) -c $<
${C_SO_NAME} : ${OBJ}
$(CC) $(MY_LDFLAGS) $^ -o $@
#export TEST_NGINX_NO_CLEAN=1
clean:; rm -f *.o *.so a.out *.d
install:
$(INSTALL) -d $(DESTDIR)$(LUA_LIB_DIR)/resty
$(INSTALL) lib/resty/*.lua $(DESTDIR)$(LUA_LIB_DIR)/resty
$(INSTALL) $(C_SO_NAME) $(DESTDIR)$(LUA_LIB_DIR)/
test : all
PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r $(test)

View File

@ -0,0 +1,132 @@
Name
====
lua-resty-signal - Lua library for killing or sending signals to Linux processes
Table of Contents
=================
* [Name](#name)
* [Synopsis](#synopsis)
* [Functions](#functions)
* [kill](#kill)
* [signum](#signum)
* [Author](#author)
* [Copyright & Licenses](#copyright--licenses)
Synopsis
========
```lua
local resty_signal = require "resty.signal"
local pid = 12345
local ok, err = resty_signal.kill(pid, "TERM")
if not ok then
ngx.log(ngx.ERR, "failed to kill process of pid ", pid, ": ", err)
return
end
-- send the signal 0 to check the existence of a process
local ok, err = resty_signal.kill(pid, "NONE")
local ok, err = resty_signal.kill(pid, "HUP")
local ok, err = resty_signal.kill(pid, "KILL")
```
Functions
=========
kill
----
**syntax:** `ok, err = resty_signal.kill(pid, signal_name_or_num)`
Sends a signal with its name string or number value to the process of the
specified pid.
All signal names accepted by [signum](#signum) are supported, like `HUP`,
`KILL`, and `TERM`.
Signal numbers are also supported when specifying nonportable system-specific
signals is desired.
[Back to TOC](#table-of-contents)
signum
------
**syntax:** `num = resty_signal.signum(sig_name)`
Maps the signal name specified to the system-specific signal number. Returns
`nil` if the signal name is not known.
All the POSIX and BSD signal names are supported:
```
HUP
INT
QUIT
ILL
TRAP
ABRT
BUS
FPE
KILL
USR1
SEGV
USR2
PIPE
ALRM
TERM
CHLD
CONT
STOP
TSTP
TTIN
TTOU
URG
XCPU
XFSZ
VTALRM
PROF
WINCH
IO
PWR
EMT
SYS
INFO
```
The special signal name `NONE` is also supported, which is mapped to zero (0).
[Back to TOC](#table-of-contents)
Author
======
Yichun Zhang (agentzh) <yichun@openresty.com>
[Back to TOC](#table-of-contents)
Copyright & Licenses
====================
This module is licensed under the BSD license.
Copyright (C) 2018-2019, [OpenResty Inc.](https://openresty.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[Back to TOC](#table-of-contents)

View File

@ -0,0 +1,156 @@
local _M = {
version = 0.03
}
local ffi = require "ffi"
local base = require "resty.core.base"
local C = ffi.C
local ffi_str = ffi.string
local tonumber = tonumber
local assert = assert
local errno = ffi.errno
local type = type
local new_tab = base.new_tab
local error = error
local string_format = string.format
local load_shared_lib
do
local string_gmatch = string.gmatch
local string_match = string.match
local io_open = io.open
local io_close = io.close
local cpath = package.cpath
function load_shared_lib(so_name)
local tried_paths = new_tab(32, 0)
local i = 1
for k, _ in string_gmatch(cpath, "[^;]+") do
local fpath = string_match(k, "(.*/)")
fpath = fpath .. so_name
-- Don't get me wrong, the only way to know if a file exist is
-- trying to open it.
local f = io_open(fpath)
if f ~= nil then
io_close(f)
return ffi.load(fpath)
end
tried_paths[i] = fpath
i = i + 1
end
return nil, tried_paths
end -- function
end -- do
local resty_signal, tried_paths = load_shared_lib("librestysignal.so")
if not resty_signal then
error("could not load librestysignal.so from the following paths:\n" ..
table.concat(tried_paths, "\n"), 2)
end
ffi.cdef[[
int resty_signal_signum(int num);
]]
if not pcall(function () return C.kill end) then
ffi.cdef("int kill(int32_t pid, int sig);")
end
if not pcall(function () return C.strerror end) then
ffi.cdef("char *strerror(int errnum);")
end
-- Below is just the ID numbers for each POSIX signal. We map these signal IDs
-- to system-specific signal numbers on the C land (via librestysignal.so).
local signals = {
NONE = 0,
HUP = 1,
INT = 2,
QUIT = 3,
ILL = 4,
TRAP = 5,
ABRT = 6,
BUS = 7,
FPE = 8,
KILL = 9,
USR1 = 10,
SEGV = 11,
USR2 = 12,
PIPE = 13,
ALRM = 14,
TERM = 15,
CHLD = 17,
CONT = 18,
STOP = 19,
TSTP = 20,
TTIN = 21,
TTOU = 22,
URG = 23,
XCPU = 24,
XFSZ = 25,
VTALRM = 26,
PROF = 27,
WINCH = 28,
IO = 29,
PWR = 30,
EMT = 31,
SYS = 32,
INFO = 33
}
local function signum(name)
local sig_num
if type(name) == "number" then
sig_num = name
else
local id = signals[name]
if not id then
return nil, "unknown signal name"
end
sig_num = tonumber(resty_signal.resty_signal_signum(id))
if sig_num < 0 then
error(
string_format("missing C def for signal %s = %d", name, id),
2
)
end
end
return sig_num
end
function _M.kill(pid, sig)
assert(sig)
local sig_num, err = signum(sig)
if err then
return nil, err
end
local rc = tonumber(C.kill(assert(pid), sig_num))
if rc == 0 then
return true
end
local err = ffi_str(C.strerror(errno()))
return nil, err
end
_M.signum = signum
return _M

View File

@ -0,0 +1,152 @@
#include <signal.h>
enum {
RS_NONE = 0,
RS_HUP = 1,
RS_INT = 2,
RS_QUIT = 3,
RS_ILL = 4,
RS_TRAP = 5,
RS_ABRT = 6,
RS_BUS = 7,
RS_FPE = 8,
RS_KILL = 9,
RS_USR1 = 10,
RS_SEGV = 11,
RS_USR2 = 12,
RS_PIPE = 13,
RS_ALRM = 14,
RS_TERM = 15,
RS_CHLD = 17,
RS_CONT = 18,
RS_STOP = 19,
RS_TSTP = 20,
RS_TTIN = 21,
RS_TTOU = 22,
RS_URG = 23,
RS_XCPU = 24,
RS_XFSZ = 25,
RS_VTALRM = 26,
RS_PROF = 27,
RS_WINCH = 28,
RS_IO = 29,
RS_PWR = 30,
RS_EMT = 31,
RS_SYS = 32,
RS_INFO = 33
};
int
resty_signal_signum(int num)
{
switch (num) {
case RS_NONE:
return 0;
case RS_HUP:
return SIGHUP;
case RS_INT:
return SIGINT;
case RS_QUIT:
return SIGQUIT;
case RS_ILL:
return SIGILL;
case RS_TRAP:
return SIGTRAP;
case RS_ABRT:
return SIGABRT;
case RS_BUS:
return SIGBUS;
case RS_FPE:
return SIGFPE;
case RS_KILL:
return SIGKILL;
case RS_SEGV:
return SIGSEGV;
case RS_PIPE:
return SIGPIPE;
case RS_ALRM:
return SIGALRM;
case RS_TERM:
return SIGTERM;
case RS_CHLD:
return SIGCHLD;
case RS_CONT:
return SIGCONT;
case RS_STOP:
return SIGSTOP;
case RS_TSTP:
return SIGTSTP;
case RS_TTIN:
return SIGTTIN;
case RS_TTOU:
return SIGTTOU;
case RS_XCPU:
return SIGXCPU;
case RS_XFSZ:
return SIGXFSZ;
case RS_VTALRM:
return SIGVTALRM;
case RS_PROF:
return SIGPROF;
case RS_WINCH:
return SIGWINCH;
case RS_IO:
return SIGIO;
#ifdef __linux__
case RS_PWR:
return SIGPWR;
#endif
case RS_USR1:
return SIGUSR1;
case RS_USR2:
return SIGUSR2;
case RS_URG:
return SIGURG;
#ifdef __APPLE__
case RS_EMT:
return SIGEMT;
case RS_SYS:
return SIGSYS;
case RS_INFO:
return SIGINFO;
#endif
default:
return -1;
}
}

View File

@ -0,0 +1,32 @@
package t::TestKiller;
use v5.10.1;
use Test::Nginx::Socket::Lua -Base;
add_block_preprocessor(sub {
my $block = shift;
my $http_config = $block->http_config // '';
my $init_by_lua_block = $block->init_by_lua_block // 'require "resty.core"';
$http_config .= <<_EOC_;
lua_package_path "./lib/?.lua;../lua-resty-core/lib/?.lua;../lua-resty-lrucache/lib/?.lua;;";
lua_package_cpath "./?.so;;";
init_by_lua_block {
$init_by_lua_block
}
_EOC_
$block->set_value("http_config", $http_config);
if (!defined $block->error_log) {
$block->set_value("no_error_log", "[error]");
}
if (!defined $block->request) {
$block->set_value("request", "GET /t");
}
});
1;

View File

@ -0,0 +1,207 @@
# vi:ft=
use lib '.';
use t::TestKiller;
plan tests => 3 * blocks();
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: returns an error if signal is unknown
--- config
location = /t {
content_by_lua_block {
local resty_signal = require "resty.signal"
local ok, err = resty_signal.kill(pid, "FOO")
if not ok then
ngx.say("failed to send FOO signal: ", err)
return
end
ngx.say("ok")
}
}
--- response_body
failed to send FOO signal: unknown signal name
=== TEST 2: send NONE to a non-existing process
--- config
location = /t {
content_by_lua_block {
local resty_signal = require "resty.signal"
local say = ngx.say
local ngx_pipe = require "ngx.pipe"
local proc = assert(ngx_pipe.spawn("echo ok"))
local pid = assert(proc:pid())
assert(proc:wait())
local ok, err = resty_signal.kill(pid, "NONE")
if not ok then
ngx.say("failed to send NONE signal: ", err)
return
end
ngx.say("ok")
}
}
--- response_body
failed to send NONE signal: No such process
=== TEST 3: send TERM to a non-existing process
--- config
location = /t {
content_by_lua_block {
local resty_signal = require "resty.signal"
local say = ngx.say
local ngx_pipe = require "ngx.pipe"
local proc = assert(ngx_pipe.spawn("echo ok"))
local pid = assert(proc:pid())
assert(proc:wait())
local ok, err = resty_signal.kill(pid, "TERM")
if not ok then
ngx.say("failed to send TERM signal: ", err)
return
end
ngx.say("ok")
}
}
--- response_body
failed to send TERM signal: No such process
=== TEST 4: send NONE to an existing process
--- config
location = /t {
content_by_lua_block {
local resty_signal = require "resty.signal"
local say = ngx.say
local ngx_pipe = require "ngx.pipe"
local proc = assert(ngx_pipe.spawn("echo ok"))
local pid = assert(proc:pid())
-- assert(proc:wait())
for i = 1, 2 do
ngx.say("i = ", i)
local ok, err = resty_signal.kill(pid, "NONE")
if not ok then
ngx.say("failed to send NONE signal: ", err)
return
end
end
ngx.say("ok")
}
}
--- response_body
i = 1
i = 2
ok
=== TEST 5: send TERM to an existing process
--- config
location = /t {
content_by_lua_block {
local resty_signal = require "resty.signal"
local say = ngx.say
local ngx_pipe = require "ngx.pipe"
local proc = assert(ngx_pipe.spawn("echo ok"))
local pid = assert(proc:pid())
-- assert(proc:wait())
for i = 1, 2 do
ngx.say("i = ", i)
local ok, err = resty_signal.kill(pid, "TERM")
if not ok then
ngx.say("failed to send TERM signal: ", err)
return
end
ngx.sleep(0.01)
end
ngx.say("ok")
}
}
--- response_body
i = 1
i = 2
failed to send TERM signal: No such process
=== TEST 6: send KILL to an existing process
--- config
location = /t {
content_by_lua_block {
local resty_signal = require "resty.signal"
local say = ngx.say
local ngx_pipe = require "ngx.pipe"
local proc = assert(ngx_pipe.spawn("echo ok"))
local pid = assert(proc:pid())
-- assert(proc:wait())
for i = 1, 2 do
ngx.say("i = ", i)
local ok, err = resty_signal.kill(pid, "KILL")
if not ok then
ngx.say("failed to send KILL signal: ", err)
return
end
ngx.sleep(0.01)
end
ngx.say("ok")
}
}
--- response_body
i = 1
i = 2
failed to send KILL signal: No such process
=== TEST 7: send TERM signal value, 15, directly to an existing process
--- config
location = /t {
content_by_lua_block {
local resty_signal = require "resty.signal"
local say = ngx.say
local ngx_pipe = require "ngx.pipe"
local proc = assert(ngx_pipe.spawn("echo ok"))
local pid = assert(proc:pid())
-- assert(proc:wait())
for i = 1, 2 do
ngx.say("i = ", i)
local ok, err = resty_signal.kill(pid, 15)
if not ok then
ngx.say("failed to send TERM signal: ", err)
return
end
ngx.sleep(0.01)
end
ngx.say("ok")
}
}
--- response_body
i = 1
i = 2
failed to send TERM signal: No such process

View File

@ -0,0 +1,35 @@
# vi:ft=
use lib '.';
use t::TestKiller;
plan tests => 3 * blocks();
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: failure to load librestysignal.so
--- config
location = /t {
content_by_lua_block {
local cpath = package.cpath
package.cpath = "/foo/?.so;/bar/?.so;"
local ok, perr = pcall(require, "resty.signal")
if not ok then
ngx.say(perr)
end
package.cpath = cpath
}
}
--- response_body
could not load librestysignal.so from the following paths:
/foo/librestysignal.so
/bar/librestysignal.so
--- no_error_log
[error]

View File

@ -0,0 +1,116 @@
# vi:ft=
use lib '.';
use t::TestKiller;
plan tests => 3 * blocks();
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: signals whose values are specified by POSIX
--- config
location = /t {
content_by_lua_block {
local resty_signal = require "resty.signal"
local ffi = require "ffi"
local say = ngx.say
local signum = resty_signal.signum
for i, signame in ipairs{ "ABRT", "ALRM", "HUP", "INT", "KILL",
"QUIT", "TERM", "TRAP", "BLAH" } do
say(signame, ": ", tostring(signum(signame)))
end
local linux_signals = {
NONE = 0,
HUP = 1,
INT = 2,
QUIT = 3,
ILL = 4,
TRAP = 5,
ABRT = 6,
BUS = 7,
FPE = 8,
KILL = 9,
USR1 = 10,
SEGV = 11,
USR2 = 12,
PIPE = 13,
ALRM = 14,
TERM = 15,
CHLD = 17,
CONT = 18,
STOP = 19,
TSTP = 20,
TTIN = 21,
TTOU = 22,
URG = 23,
XCPU = 24,
XFSZ = 25,
VTALRM = 26,
PROF = 27,
WINCH = 28,
IO = 29,
PWR = 30
}
local macosx_signals = {
HUP = 1,
INT = 2,
QUIT = 3,
ILL = 4,
TRAP = 5,
ABRT = 6,
EMT = 7,
FPE = 8,
KILL = 9,
BUS = 10,
SEGV = 11,
SYS = 12,
PIPE = 13,
ALRM = 14,
TERM = 15,
URG = 16,
STOP = 17,
TSTP = 18,
CONT = 19,
CHLD = 20,
TTIN = 21,
TTOU = 22,
IO = 23,
XCPU = 24,
XFSZ = 25,
VTALRM = 26,
PROF = 27,
WINCH = 28,
INFO = 29,
USR1 = 30,
USR2 = 31
}
if ffi.os == "Linux" then
for signame, num in pairs(linux_signals) do
assert(num == tonumber(signum(signame)))
end
elseif ffi.os == "OSX" then
for signame, num in pairs(macosx_signals) do
assert(num == tonumber(signum(signame)))
end
end
}
}
--- response_body
ABRT: 6
ALRM: 14
HUP: 1
INT: 2
KILL: 9
QUIT: 3
TERM: 15
TRAP: 5
BLAH: nil

View File

@ -0,0 +1,30 @@
{
<insert_a_suppression_name_here>
Memcheck:Param
epoll_ctl(event)
fun:epoll_ctl
fun:ngx_epoll_test_rdhup
fun:ngx_epoll_init
fun:ngx_event_process_init
fun:ngx_single_process_cycle
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:ngx_alloc
fun:ngx_set_environment
fun:ngx_single_process_cycle
fun:main
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:ngx_alloc
fun:ngx_set_environment
fun:ngx_worker_process_init
}

View File

@ -38,4 +38,4 @@
ansible.builtin.pause:
seconds: 60
- name: Restart GH runner
shell: systemctl restart actions.runner.*
shell: chown -R user:user /opt/actions-runner/ && systemctl restart actions.runner.*

View File

@ -38,4 +38,4 @@
ansible.builtin.pause:
seconds: 60
- name: Restart GH runner
shell: systemctl restart actions.runner.*
shell: chown -R user:user /opt/actions-runner/ && systemctl restart actions.runner.*

View File

@ -38,4 +38,4 @@
ansible.builtin.pause:
seconds: 60
- name: Restart GH runner
shell: systemctl restart actions.runner.*
shell: chown -R user:user /opt/actions-runner/ && systemctl restart actions.runner.*

View File

@ -49,4 +49,4 @@
ansible.builtin.pause:
seconds: 60
- name: Restart GH runner
shell: systemctl restart actions.runner.*
shell: chown -R user:user /opt/actions-runner/ && systemctl restart actions.runner.*

View File

@ -0,0 +1,14 @@
FROM python:3.11.3-alpine
WORKDIR /tmp
COPY requirements.txt .
RUN MAKEFLAGS="-j $(nproc)" pip install --no-cache -r requirements.txt && \
rm -f requirements.txt
WORKDIR /opt/tests
COPY main.py .
ENTRYPOINT [ "python3", "main.py" ]

View File

@ -0,0 +1,7 @@
version: "3.5"
services:
tests:
build: .
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro

View File

@ -0,0 +1,55 @@
version: "3.5"
services:
bw:
image: bunkerity/bunkerweb:1.5.0-beta
pull_policy: never
depends_on:
- bw-redis
labels:
- "bunkerweb.INSTANCE"
environment:
API_WHITELIST_IP: "127.0.0.0/8 10.20.30.0/24"
USE_BUNKERNET: "no"
USE_BLACKLIST: "no"
LOG_LEVEL: "info"
USE_REDIS: "yes"
REDIS_HOST: "bw-redis"
networks:
- bw-universe
bw-scheduler:
image: bunkerity/bunkerweb-scheduler:1.5.0-beta
pull_policy: never
depends_on:
- bw
- bw-docker
environment:
DOCKER_HOST: "tcp://bw-docker:2375"
LOG_LEVEL: "info"
networks:
- bw-universe
- bw-docker
bw-docker:
image: tecnativa/docker-socket-proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CONTAINERS: "1"
networks:
- bw-docker
bw-redis:
image: redis:7-alpine
networks:
- bw-universe
networks:
bw-universe:
name: bw-universe
ipam:
driver: default
config:
- subnet: 10.20.30.0/24
bw-docker:

111
tests/core/bwcli/main.py Normal file
View File

@ -0,0 +1,111 @@
from os import getenv
from traceback import format_exc
from docker import DockerClient
from docker.models.containers import Container
try:
docker_host = getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
docker_client = DockerClient(base_url=docker_host)
bw_instances = docker_client.containers.list(
filters={"label": "bunkerweb.INSTANCE"}
)
if not bw_instances:
print("❌ BunkerWeb instance not found ...", flush=True)
exit(1)
bw_instance: Container = bw_instances[0]
print(
' Executing the command "bwcli ban 127.0.0.1 -exp 3600" inside the BW container ...',
flush=True,
)
result = bw_instance.exec_run("bwcli ban 127.0.0.1 -exp 3600")
if result.exit_code != 0:
print(
f'❌ Command "ban" failed, exiting ...\noutput: {result.output.decode()}\nexit_code: {result.exit_code}'
)
exit(1)
print(result.output.decode(), flush=True)
print(
' Executing the command "bwcli bans" inside the BW container and checking the result ...',
flush=True,
)
result = bw_instance.exec_run("bwcli bans")
if result.exit_code != 0:
print(
f'❌ Command "bans" failed, exiting ...\noutput: {result.output.decode()}\nexit_code: {result.exit_code}'
)
exit(1)
if b"- 127.0.0.1" not in result.output:
print(
f'❌ IP 127.0.0.1 not found in the output of "bans", exiting ...\noutput: {result.output.decode()}'
)
exit(1)
elif b"List of bans for redis:" not in result.output:
print(
f'❌ Redis ban list not found in the output of "bans", exiting ...\noutput: {result.output.decode()}'
)
exit(1)
elif b"1 hour" not in result.output and b"59 minutes" not in result.output:
print(
f"❌ Ban duration isn't 1 hour, exiting ...\noutput: {result.output.decode()}"
)
exit(1)
print(result.output.decode(), flush=True)
print(
' Executing the command "bwcli unban 127.0.0.1" inside the BW container ...',
flush=True,
)
result = bw_instance.exec_run("bwcli unban 127.0.0.1")
if result.exit_code != 0:
print(
f'❌ Command "unban" failed, exiting ...\noutput: {result.output.decode()}\nexit_code: {result.exit_code}'
)
exit(1)
print(result.output.decode(), flush=True)
print(
' Executing the command "bwcli bans" inside the BW container to check if the IP was unbanned ...',
flush=True,
)
result = bw_instance.exec_run("bwcli bans")
if result.exit_code != 0:
print(
f'❌ Command "bans" failed, exiting ...\noutput: {result.output.decode()}\nexit_code: {result.exit_code}'
)
exit(1)
found = 0
for line in result.output.splitlines():
if b"No ban found" in line:
found += 1
if found < 2:
print(
f"❌ IP 127.0.0.1 was not unbanned from both redis and the local ban list, exiting ...\noutput: {result.output.decode()}",
flush=True,
)
exit(1)
print(result.output.decode(), flush=True)
except SystemExit:
exit(1)
except:
print(f"❌ Something went wrong, exiting ...\n{format_exc()}", flush=True)
exit(1)

View File

@ -0,0 +1 @@
docker==6.1.2

88
tests/core/bwcli/test.sh Executable file
View File

@ -0,0 +1,88 @@
#!/bin/bash
echo "⌨️ Building bunkernet stack ..."
# Starting stack
docker compose pull bw-docker
if [ $? -ne 0 ] ; then
echo "⌨️ Pull failed ❌"
exit 1
fi
docker compose -f docker-compose.test.yml build
if [ $? -ne 0 ] ; then
echo "⌨️ Build failed ❌"
exit 1
fi
cleanup_stack () {
echo "⌨️ Cleaning up current stack ..."
docker compose down -v --remove-orphans 2>/dev/null
if [ $? -ne 0 ] ; then
echo "⌨️ Down failed ❌"
exit 1
fi
echo "⌨️ Cleaning up current stack done ✅"
}
# Cleanup stack on exit
trap cleanup_stack EXIT
echo "⌨️ Running bwcli tests ..."
echo "⌨️ Starting stack ..."
docker compose up -d 2>/dev/null
if [ $? -ne 0 ] ; then
echo "⌨️ Up failed, retrying ... ⚠️"
manual=1
cleanup_stack
manual=0
docker compose up -d 2>/dev/null
if [ $? -ne 0 ] ; then
echo "⌨️ Up failed ❌"
exit 1
fi
fi
# Check if stack is healthy
echo "⌨️ Waiting for stack to be healthy ..."
i=0
while [ $i -lt 120 ] ; do
containers=("bwcli-bw-1" "bwcli-bw-scheduler-1")
healthy="true"
for container in "${containers[@]}" ; do
check="$(docker inspect --format "{{json .State.Health }}" $container | grep "healthy")"
if [ "$check" = "" ] ; then
healthy="false"
break
fi
done
if [ "$healthy" = "true" ] ; then
echo "⌨️ Docker stack is healthy ✅"
break
fi
sleep 1
i=$((i+1))
done
if [ $i -ge 120 ] ; then
docker compose logs
echo "⌨️ Docker stack is not healthy ❌"
exit 1
fi
# Start tests
docker compose -f docker-compose.test.yml up --abort-on-container-exit --exit-code-from tests 2>/dev/null
if [ $? -ne 0 ] ; then
echo "⌨️ Test bwcli failed ❌"
echo "🛡️ Showing BunkerWeb and BunkerWeb Scheduler logs ..."
docker compose logs bw bw-scheduler
exit 1
else
echo "⌨️ Test bwcli succeeded ✅"
fi
echo "⌨️ Tests are done ! ✅"

View File

@ -812,7 +812,7 @@ with webdriver.Firefox(
assert_alert_message(driver, "was successfully created")
sleep(15)
sleep(30)
driver.execute_script("window.open('http://www.example.com/hello','_blank');")
driver.switch_to.window(driver.window_handles[1])