diff --git a/TODO b/TODO
index 49a283cf8..7e1bb3dc0 100644
--- a/TODO
+++ b/TODO
@@ -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)
diff --git a/docs/assets/img/integration-ansible.svg b/docs/assets/img/integration-ansible.svg
index 18cf91235..c41117d5f 100644
--- a/docs/assets/img/integration-ansible.svg
+++ b/docs/assets/img/integration-ansible.svg
@@ -1,4 +1 @@
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/assets/img/integration-linux.svg b/docs/assets/img/integration-linux.svg
index b638fa28b..f0c8821a0 100644
--- a/docs/assets/img/integration-linux.svg
+++ b/docs/assets/img/integration-linux.svg
@@ -1,4 +1 @@
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/security-tuning.md b/docs/security-tuning.md
index 3ebbd4e97..33cde0c0d 100644
--- a/docs/security-tuning.md
+++ b/docs/security-tuning.md
@@ -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
diff --git a/docs/settings.md b/docs/settings.md
index 146251c53..fecd02f9f 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -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
diff --git a/misc/integrations/k8s.mysql.ui.yml b/misc/integrations/k8s.mysql.ui.yml
index 893681a83..8e9371368 100644
--- a/misc/integrations/k8s.mysql.ui.yml
+++ b/misc/integrations/k8s.mysql.ui.yml
@@ -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
diff --git a/misc/integrations/k8s.mysql.yml b/misc/integrations/k8s.mysql.yml
index 8f83e63bb..c163cb704 100644
--- a/misc/integrations/k8s.mysql.yml
+++ b/misc/integrations/k8s.mysql.yml
@@ -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
diff --git a/misc/integrations/k8s.postgres.ui.yml b/misc/integrations/k8s.postgres.ui.yml
index 50ffaa3e3..ce2a97668 100644
--- a/misc/integrations/k8s.postgres.ui.yml
+++ b/misc/integrations/k8s.postgres.ui.yml
@@ -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
diff --git a/misc/integrations/k8s.postgres.yml b/misc/integrations/k8s.postgres.yml
index a73ebafc0..f54b8af9e 100644
--- a/misc/integrations/k8s.postgres.yml
+++ b/misc/integrations/k8s.postgres.yml
@@ -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
diff --git a/src/bw/lua/bunkerweb/api.lua b/src/bw/lua/bunkerweb/api.lua
index ce096abb2..3139ba6b0 100644
--- a/src/bw/lua/bunkerweb/api.lua
+++ b/src/bw/lua/bunkerweb/api.lua
@@ -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
diff --git a/src/bw/lua/bunkerweb/cachestore.lua b/src/bw/lua/bunkerweb/cachestore.lua
index db46caa83..0041af7df 100644
--- a/src/bw/lua/bunkerweb/cachestore.lua
+++ b/src/bw/lua/bunkerweb/cachestore.lua
@@ -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
diff --git a/src/bw/lua/bunkerweb/clusterstore.lua b/src/bw/lua/bunkerweb/clusterstore.lua
index 56490979b..1f96aabc3 100644
--- a/src/bw/lua/bunkerweb/clusterstore.lua
+++ b/src/bw/lua/bunkerweb/clusterstore.lua
@@ -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"
diff --git a/src/bw/lua/bunkerweb/helpers.lua b/src/bw/lua/bunkerweb/helpers.lua
index a00a6dd12..e2248039b 100644
--- a/src/bw/lua/bunkerweb/helpers.lua
+++ b/src/bw/lua/bunkerweb/helpers.lua
@@ -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
diff --git a/src/bw/lua/bunkerweb/plugin.lua b/src/bw/lua/bunkerweb/plugin.lua
index b72a08d19..b4e791134 100644
--- a/src/bw/lua/bunkerweb/plugin.lua
+++ b/src/bw/lua/bunkerweb/plugin.lua
@@ -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
diff --git a/src/bw/lua/bunkerweb/utils.lua b/src/bw/lua/bunkerweb/utils.lua
index a14a63d24..95c0ed7e1 100644
--- a/src/bw/lua/bunkerweb/utils.lua
+++ b/src/bw/lua/bunkerweb/utils.lua
@@ -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
diff --git a/src/common/confs/default-server-http.conf b/src/common/confs/default-server-http.conf
index b3e626030..e6f600c46 100644
--- a/src/common/confs/default-server-http.conf
+++ b/src/common/confs/default-server-http.conf
@@ -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)
diff --git a/src/common/confs/http.conf b/src/common/confs/http.conf
index a4fc165ea..8494be3bb 100644
--- a/src/common/confs/http.conf
+++ b/src/common/confs/http.conf
@@ -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;
diff --git a/src/common/confs/init-lua.conf b/src/common/confs/init-lua.conf
index 00267fb27..66831f70c 100644
--- a/src/common/confs/init-lua.conf
+++ b/src/common/confs/init-lua.conf
@@ -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")
diff --git a/src/common/confs/init-stream-lua.conf b/src/common/confs/init-stream-lua.conf
index f3a2b7652..3b994be18 100644
--- a/src/common/confs/init-stream-lua.conf
+++ b/src/common/confs/init-stream-lua.conf
@@ -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")
-
- }
-
\ No newline at end of file
+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")
+
+}
diff --git a/src/common/confs/init-worker-lua.conf b/src/common/confs/init-worker-lua.conf
index b5d85ffa0..b7629f12c 100644
--- a/src/common/confs/init-worker-lua.conf
+++ b/src/common/confs/init-worker-lua.conf
@@ -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
diff --git a/src/common/confs/server-http/access-lua.conf b/src/common/confs/server-http/access-lua.conf
index c697ed3f5..945296bbf 100644
--- a/src/common/confs/server-http/access-lua.conf
+++ b/src/common/confs/server-http/access-lua.conf
@@ -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)
diff --git a/src/common/confs/server-http/header-lua.conf b/src/common/confs/server-http/header-lua.conf
index 8b8f1c2bb..a8bf6ff80 100644
--- a/src/common/confs/server-http/header-lua.conf
+++ b/src/common/confs/server-http/header-lua.conf
@@ -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)
diff --git a/src/common/confs/server-http/log-lua.conf b/src/common/confs/server-http/log-lua.conf
index 5689676f9..bb169996c 100644
--- a/src/common/confs/server-http/log-lua.conf
+++ b/src/common/confs/server-http/log-lua.conf
@@ -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)
diff --git a/src/common/confs/server-http/set-lua.conf b/src/common/confs/server-http/set-lua.conf
index aa6461663..4262454e5 100644
--- a/src/common/confs/server-http/set-lua.conf
+++ b/src/common/confs/server-http/set-lua.conf
@@ -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)
diff --git a/src/common/confs/server-stream/log-stream-lua.conf b/src/common/confs/server-stream/log-stream-lua.conf
index 97882dee6..ee9da1e46 100644
--- a/src/common/confs/server-stream/log-stream-lua.conf
+++ b/src/common/confs/server-stream/log-stream-lua.conf
@@ -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)
diff --git a/src/common/confs/server-stream/preread-stream-lua.conf b/src/common/confs/server-stream/preread-stream-lua.conf
index bc151332e..b04bbb0fa 100644
--- a/src/common/confs/server-stream/preread-stream-lua.conf
+++ b/src/common/confs/server-stream/preread-stream-lua.conf
@@ -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
diff --git a/src/common/confs/stream.conf b/src/common/confs/stream.conf
index bc3303926..bfa09d7a8 100644
--- a/src/common/confs/stream.conf
+++ b/src/common/confs/stream.conf
@@ -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;
diff --git a/src/common/core/antibot/antibot.lua b/src/common/core/antibot/antibot.lua
index bda29796c..07c235324 100644
--- a/src/common/core/antibot/antibot.lua
+++ b/src/common/core/antibot/antibot.lua
@@ -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()
diff --git a/src/common/core/badbehavior/badbehavior.lua b/src/common/core/badbehavior/badbehavior.lua
index 67277f931..11dccdd2f 100644
--- a/src/common/core/badbehavior/badbehavior.lua
+++ b/src/common/core/badbehavior/badbehavior.lua
@@ -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])
diff --git a/src/common/core/blacklist/blacklist.lua b/src/common/core/blacklist/blacklist.lua
index 2d99f637f..ea332ec95 100644
--- a/src/common/core/blacklist/blacklist.lua
+++ b/src/common/core/blacklist/blacklist.lua
@@ -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
diff --git a/src/common/core/clientcache/plugin.json b/src/common/core/clientcache/plugin.json
index 82af93dff..74b6331af 100644
--- a/src/common/core/clientcache/plugin.json
+++ b/src/common/core/clientcache/plugin.json
@@ -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"
}
}
diff --git a/src/common/core/country/country.lua b/src/common/core/country/country.lua
index f02cb351b..9b7c56d41 100644
--- a/src/common/core/country/country.lua
+++ b/src/common/core/country/country.lua
@@ -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()
diff --git a/src/common/core/dnsbl/dnsbl.lua b/src/common/core/dnsbl/dnsbl.lua
index 1bc2a02cd..614bf2175 100644
--- a/src/common/core/dnsbl/dnsbl.lua
+++ b/src/common/core/dnsbl/dnsbl.lua
@@ -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
diff --git a/src/common/core/greylist/greylist.lua b/src/common/core/greylist/greylist.lua
index 0817c5624..0ec9d710a 100644
--- a/src/common/core/greylist/greylist.lua
+++ b/src/common/core/greylist/greylist.lua
@@ -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
diff --git a/src/common/core/limit/limit.lua b/src/common/core/limit/limit.lua
index 6621a8197..61bee7b6e 100644
--- a/src/common/core/limit/limit.lua
+++ b/src/common/core/limit/limit.lua
@@ -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
diff --git a/src/common/core/redis/redis.lua b/src/common/core/redis/redis.lua
index 3f5102283..eb911da7f 100644
--- a/src/common/core/redis/redis.lua
+++ b/src/common/core/redis/redis.lua
@@ -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()
diff --git a/src/common/core/reversescan/reversescan.lua b/src/common/core/reversescan/reversescan.lua
index 11a5dd2d5..d76d9cb18 100644
--- a/src/common/core/reversescan/reversescan.lua
+++ b/src/common/core/reversescan/reversescan.lua
@@ -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)
diff --git a/src/common/core/sessions/plugin.json b/src/common/core/sessions/plugin.json
index 7c94a9e4d..a24f7fdfc 100644
--- a/src/common/core/sessions/plugin.json
+++ b/src/common/core/sessions/plugin.json
@@ -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"
}
}
}
diff --git a/src/common/core/sessions/sessions.lua b/src/common/core/sessions/sessions.lua
index c042f07fa..adb29c39e 100644
--- a/src/common/core/sessions/sessions.lua
+++ b/src/common/core/sessions/sessions.lua
@@ -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"],
diff --git a/src/common/core/whitelist/whitelist.lua b/src/common/core/whitelist/whitelist.lua
index 644be9801..289817f66 100644
--- a/src/common/core/whitelist/whitelist.lua
+++ b/src/common/core/whitelist/whitelist.lua
@@ -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
diff --git a/src/deps/clone.sh b/src/deps/clone.sh
index 20d7077f1..cfc2a221d 100755
--- a/src/deps/clone.sh
+++ b/src/deps/clone.sh
@@ -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"
diff --git a/src/deps/install.sh b/src/deps/install.sh
index 23ddababc..afdf9c822 100755
--- a/src/deps/install.sh
+++ b/src/deps/install.sh
@@ -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')"
diff --git a/src/deps/src/lua-resty-openssl/CHANGELOG.md b/src/deps/src/lua-resty-openssl/CHANGELOG.md
index e785b7d68..0aadc4dfe 100644
--- a/src/deps/src/lua-resty-openssl/CHANGELOG.md
+++ b/src/deps/src/lua-resty-openssl/CHANGELOG.md
@@ -2,6 +2,12 @@
## [Unreleased]
+
+## [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)
+
+
## [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
diff --git a/src/deps/src/lua-resty-openssl/Makefile b/src/deps/src/lua-resty-openssl/Makefile
index e5e8e8042..39fd2ef28 100644
--- a/src/deps/src/lua-resty-openssl/Makefile
+++ b/src/deps/src/lua-resty-openssl/Makefile
@@ -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
diff --git a/src/deps/src/lua-resty-openssl/lib/resty/openssl.lua b/src/deps/src/lua-resty-openssl/lib/resty/openssl.lua
index a786b4625..d8b153ab8 100644
--- a/src/deps/src/lua-resty-openssl/lib/resty/openssl.lua
+++ b/src/deps/src/lua-resty-openssl/lib/resty/openssl.lua
@@ -25,7 +25,7 @@ try_require_modules()
local _M = {
- _VERSION = '0.8.21',
+ _VERSION = '0.8.22',
}
local libcrypto_name
diff --git a/src/deps/src/lua-resty-openssl/lib/resty/openssl/include/crypto.lua b/src/deps/src/lua-resty-openssl/lib/resty/openssl/include/crypto.lua
index 6ca1f0801..29a6a2273 100644
--- a/src/deps/src/lua-resty-openssl/lib/resty/openssl/include/crypto.lua
+++ b/src/deps/src/lua-resty-openssl/lib/resty/openssl/include/crypto.lua
@@ -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);
diff --git a/src/deps/src/lua-resty-openssl/lua-resty-openssl-0.8.21-1.rockspec b/src/deps/src/lua-resty-openssl/lua-resty-openssl-0.8.22-1.rockspec
similarity index 99%
rename from src/deps/src/lua-resty-openssl/lua-resty-openssl-0.8.21-1.rockspec
rename to src/deps/src/lua-resty-openssl/lua-resty-openssl-0.8.22-1.rockspec
index 1fca97ebb..68dd7dc40 100644
--- a/src/deps/src/lua-resty-openssl/lua-resty-openssl-0.8.21-1.rockspec
+++ b/src/deps/src/lua-resty-openssl/lua-resty-openssl-0.8.22-1.rockspec
@@ -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.",
diff --git a/src/deps/src/lua-resty-signal/.gitignore b/src/deps/src/lua-resty-signal/.gitignore
new file mode 100644
index 000000000..bea7b6bd4
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/.gitignore
@@ -0,0 +1,9 @@
+*~
+*.swp
+*.swo
+t/servroot*
+/go
+/reindex
+/a.lua
+*.o
+*.so
diff --git a/src/deps/src/lua-resty-signal/.travis.yml b/src/deps/src/lua-resty-signal/.travis.yml
new file mode 100644
index 000000000..f1a77323a
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/.travis.yml
@@ -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
diff --git a/src/deps/src/lua-resty-signal/Makefile b/src/deps/src/lua-resty-signal/Makefile
new file mode 100644
index 000000000..eca63d4f4
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/Makefile
@@ -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)
diff --git a/src/deps/src/lua-resty-signal/README.md b/src/deps/src/lua-resty-signal/README.md
new file mode 100644
index 000000000..93f89a856
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/README.md
@@ -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)
+
+[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)
diff --git a/src/deps/src/lua-resty-signal/lib/resty/signal.lua b/src/deps/src/lua-resty-signal/lib/resty/signal.lua
new file mode 100644
index 000000000..ad11596c1
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/lib/resty/signal.lua
@@ -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
diff --git a/src/deps/src/lua-resty-signal/resty_signal.c b/src/deps/src/lua-resty-signal/resty_signal.c
new file mode 100644
index 000000000..78960c84d
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/resty_signal.c
@@ -0,0 +1,152 @@
+#include
+
+
+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;
+ }
+}
diff --git a/src/deps/src/lua-resty-signal/t/TestKiller.pm b/src/deps/src/lua-resty-signal/t/TestKiller.pm
new file mode 100644
index 000000000..5599944a1
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/t/TestKiller.pm
@@ -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;
diff --git a/src/deps/src/lua-resty-signal/t/kill.t b/src/deps/src/lua-resty-signal/t/kill.t
new file mode 100644
index 000000000..6b0d6171f
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/t/kill.t
@@ -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
diff --git a/src/deps/src/lua-resty-signal/t/sanity.t b/src/deps/src/lua-resty-signal/t/sanity.t
new file mode 100644
index 000000000..07e2cee7a
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/t/sanity.t
@@ -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]
diff --git a/src/deps/src/lua-resty-signal/t/signum.t b/src/deps/src/lua-resty-signal/t/signum.t
new file mode 100644
index 000000000..3d7b94b10
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/t/signum.t
@@ -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
diff --git a/src/deps/src/lua-resty-signal/valgrind.suppress b/src/deps/src/lua-resty-signal/valgrind.suppress
new file mode 100644
index 000000000..1cac50666
--- /dev/null
+++ b/src/deps/src/lua-resty-signal/valgrind.suppress
@@ -0,0 +1,30 @@
+{
+
+ 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
+}
+{
+
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_set_environment
+ fun:ngx_single_process_cycle
+ fun:main
+}
+{
+
+ Memcheck:Leak
+ match-leak-kinds: definite
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_set_environment
+ fun:ngx_worker_process_init
+}
diff --git a/tests/ansible/autoconf_playbook b/tests/ansible/autoconf_playbook
index 39dd3ecc5..74d1c6e59 100644
--- a/tests/ansible/autoconf_playbook
+++ b/tests/ansible/autoconf_playbook
@@ -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.*
diff --git a/tests/ansible/docker_playbook b/tests/ansible/docker_playbook
index b297bd15c..36c15d4a3 100644
--- a/tests/ansible/docker_playbook
+++ b/tests/ansible/docker_playbook
@@ -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.*
diff --git a/tests/ansible/linux_playbook b/tests/ansible/linux_playbook
index e5daa8394..73717f4ea 100644
--- a/tests/ansible/linux_playbook
+++ b/tests/ansible/linux_playbook
@@ -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.*
diff --git a/tests/ansible/swarm_playbook b/tests/ansible/swarm_playbook
index 03e86f713..eb55c2a19 100644
--- a/tests/ansible/swarm_playbook
+++ b/tests/ansible/swarm_playbook
@@ -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.*
diff --git a/tests/core/bwcli/Dockerfile b/tests/core/bwcli/Dockerfile
new file mode 100644
index 000000000..4a4619dce
--- /dev/null
+++ b/tests/core/bwcli/Dockerfile
@@ -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" ]
diff --git a/tests/core/bwcli/docker-compose.test.yml b/tests/core/bwcli/docker-compose.test.yml
new file mode 100644
index 000000000..88a7243a8
--- /dev/null
+++ b/tests/core/bwcli/docker-compose.test.yml
@@ -0,0 +1,7 @@
+version: "3.5"
+
+services:
+ tests:
+ build: .
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock:ro
diff --git a/tests/core/bwcli/docker-compose.yml b/tests/core/bwcli/docker-compose.yml
new file mode 100644
index 000000000..52fecbd38
--- /dev/null
+++ b/tests/core/bwcli/docker-compose.yml
@@ -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:
diff --git a/tests/core/bwcli/main.py b/tests/core/bwcli/main.py
new file mode 100644
index 000000000..87139958c
--- /dev/null
+++ b/tests/core/bwcli/main.py
@@ -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)
diff --git a/tests/core/bwcli/requirements.txt b/tests/core/bwcli/requirements.txt
new file mode 100644
index 000000000..837e8178b
--- /dev/null
+++ b/tests/core/bwcli/requirements.txt
@@ -0,0 +1 @@
+docker==6.1.2
diff --git a/tests/core/bwcli/test.sh b/tests/core/bwcli/test.sh
new file mode 100755
index 000000000..e0b645970
--- /dev/null
+++ b/tests/core/bwcli/test.sh
@@ -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 ! ✅"
diff --git a/tests/ui/main.py b/tests/ui/main.py
index 2551f9882..15d376d1b 100644
--- a/tests/ui/main.py
+++ b/tests/ui/main.py
@@ -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])