-
Notifications
You must be signed in to change notification settings - Fork 637
Expand file tree
/
Copy pathssl-certificate-lua.conf
More file actions
304 lines (278 loc) · 11.5 KB
/
Copy pathssl-certificate-lua.conf
File metadata and controls
304 lines (278 loc) · 11.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
ssl_certificate /var/cache/bunkerweb/misc/default-server-cert.pem;
ssl_certificate_key /var/cache/bunkerweb/misc/default-server-cert.key;
ssl_protocols {{ SSL_PROTOCOLS }};
{% if SSL_PROTOCOLS == "TLSv1.3" %}
ssl_prefer_server_ciphers off;
{% else %}
ssl_prefer_server_ciphers on;
{% endif %}
ssl_session_tickets off;
{% if "TLSv1.2" in SSL_PROTOCOLS or "TLSv1.1" in SSL_PROTOCOLS or "TLSv1" in SSL_PROTOCOLS %}
ssl_session_timeout 1d;
{% if SSL_SESSION_CACHE_SIZE == "off" or SSL_SESSION_CACHE_SIZE == "none" %}
ssl_session_cache {{ SSL_SESSION_CACHE_SIZE }};
{% else %}
ssl_session_cache shared:MozSSL:{{ SSL_SESSION_CACHE_SIZE }};
{% endif %}
{% endif %}
ssl_ecdh_curve {{ resolve_ssl_ecdh_curve(SSL_ECDH_CURVE) }};
{% if "TLSv1.2" in SSL_PROTOCOLS +%}
ssl_dhparam /etc/nginx/dhparam;
{% if SSL_CIPHERS_CUSTOM|default('')|trim != "" %}
ssl_ciphers {{ SSL_CIPHERS_CUSTOM }};
{% else %}
{% if SSL_CIPHERS_LEVEL == "old" %}
ssl_ciphers @SECLEVEL=0:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA;
{% elif SSL_CIPHERS_LEVEL == "intermediate" %}
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
{% elif SSL_PROTOCOLS != "TLSv1.3" %}
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
{% endif %}
{% endif %}
{% endif %}
{% set https_ports = [] %}
{% for k, port in all.items() if k.startswith("HTTPS_PORT") and port %}
{% if https_ports.append(port) %}{% endif %}
{% endfor %}
{% if https_ports|length > 0 %}
{% if HTTP2 == "yes" %}
http2 on;
{% endif %}
{% set common_options = " ssl" %}
{% if USE_PROXY_PROTOCOL == "yes" %}
{% set common_options = common_options ~ " proxy_protocol" %}
{% endif %}
{% for port in https_ports %}
listen 0.0.0.0:{{ port }}{{ common_options }};
{% if USE_IPV6 == "yes" %}
listen [::]:{{ port }}{{ common_options }};
{% endif %}
{% endfor %}
{% endif %}
{% if https_ports|length > 0 and "TLSv1.3" in SSL_PROTOCOLS and HTTP3 == "yes" and USE_PROXY_PROTOCOL == "no" %}
http3 on;
{% set http3_listen_options = " quic" %}
{% if MULTISITE == "no" and DISABLE_DEFAULT_SERVER == "no" and IS_LOADING == "no" %}
{% set http3_listen_options = http3_listen_options ~ " reuseport" %}
{% endif %}
{% for port in https_ports %}
listen 0.0.0.0:{{ port }}{{ http3_listen_options }};
{% if USE_IPV6 == "yes" %}
listen [::]:{{ port }}{{ http3_listen_options }};
{% endif %}
{% endfor %}
add_header Alt-Svc 'h3=":{{ HTTP3_ALT_SVC_PORT }}"; ma=86400';
{% endif %}
ssl_certificate_by_lua_block {
local class = require "middleclass"
local clogger = require "bunkerweb.logger"
local helpers = require "bunkerweb.helpers"
local utils = require "bunkerweb.utils"
local cdatastore = require "bunkerweb.datastore"
local cjson = require "cjson"
local ssl = require "ngx.ssl"
local ocsp = require "ngx.ocsp"
local ngx = ngx
local ngx_req = ngx.req
local is_internal = ngx_req.is_internal
local ERR = ngx.ERR
local INFO = ngx.INFO
local clear_certs = ssl.clear_certs
local set_cert = ssl.set_cert
local set_priv_key = ssl.set_priv_key
local require_plugin = helpers.require_plugin
local new_plugin = helpers.new_plugin
local call_plugin = helpers.call_plugin
local tostring = tostring
local insert = table.insert
local lower = string.lower
local match = string.match
local concat = table.concat
-- Start ssl_certificate phase
local logger = clogger:new("SSL-CERTIFICATE")
local internalstore = cdatastore:new(ngx.shared.internalstore)
logger:log(INFO, "ssl_certificate phase started")
-- Get plugins order
local order, err = internalstore:get("plugins_order", true)
if not order then
logger:log(ERR, "can't get plugins order from internalstore : " .. err)
return
end
-- Resolve per-site plugin order
local function get_phase_order(ord, phase, server_name)
if ord.per_site and server_name and ord.per_site[server_name] and ord.per_site[server_name][phase] then
return ord.per_site[server_name][phase]
elseif ord.global and ord.global[phase] then
return ord.global[phase]
end
return ord[phase]
end
local server_name = ssl.server_name()
local phase_order = get_phase_order(order, "ssl_certificate", server_name)
-- Helper: check if OCSP stapling is enabled for this site
local function is_ocsp_stapling_enabled()
-- TODO: Check SSL_USE_OCSP_STAPLING variable when variable loading is available in ssl_certificate phase
-- For now, always attempt OCSP stapling if files exist (graceful fallback if not)
return true
end
-- Helper: sanitize domain/cert name for filesystem (replace * with _wildcard_)
local function sanitize_name(name)
if not name then return nil end
return name:gsub("%*", "_wildcard_")
end
-- Helper: set OCSP stapling from cache (internalstore + ocsp.der disk files)
local function set_ocsp_from_cache()
logger:log(INFO, "OCSP set_ocsp_from_cache() called for server_name=" .. (server_name or "nil"))
if not is_ocsp_stapling_enabled() then
logger:log(INFO, "OCSP stapling disabled via SSL_USE_OCSP_STAPLING setting")
return
end
if not server_name or server_name == "" then
logger:log(ERR, "OCSP no server_name available")
return
end
local cache_key = "SSL:ocsp_status:" .. server_name
local resp
-- 1) Local shared dict lookup (per-worker cache)
local db_val, gerr = internalstore:get(cache_key, true)
if db_val then
resp = db_val
elseif gerr and gerr ~= "not found" then
logger:log(ERR, "OCSP error while getting response from internalstore: " .. gerr)
end
-- 2) Fallback to on-disk OCSP response (/var/cache/bunkerweb/ssl/{domain}/ocsp.der)
if not resp then
local base_path = "/var/cache/bunkerweb/ssl/"
-- Generate candidates for lookup
local candidates = {}
-- Direct matches
insert(candidates, server_name)
insert(candidates, server_name .. "-ecdsa")
insert(candidates, server_name .. "-rsa")
insert(candidates, "customcert-" .. server_name)
insert(candidates, "customcert-" .. server_name .. "-ecdsa")
insert(candidates, "customcert-" .. server_name .. "-rsa")
-- Wildcard and apex matches (e.g., if server_name is www.example.com, try *.example.com and example.com)
local labels = {}
for label in server_name:gmatch("[^.]+") do
insert(labels, label)
end
-- Only try wildcard/apex if we have at least subdomain.example.com (2+ labels)
if #labels >= 2 then
for i = 2, #labels do
local suffix = table.concat(labels, ".", i)
if suffix and suffix ~= "" then
-- Apex domain (e.g. example.com) — wildcard certs are often stored under apex name
insert(candidates, suffix)
insert(candidates, suffix .. "-ecdsa")
insert(candidates, suffix .. "-rsa")
insert(candidates, "customcert-" .. suffix)
insert(candidates, "customcert-" .. suffix .. "-ecdsa")
insert(candidates, "customcert-" .. suffix .. "-rsa")
-- Wildcard form (e.g. *.example.com)
local wildcard_base = "*." .. suffix
insert(candidates, wildcard_base)
insert(candidates, wildcard_base .. "-ecdsa")
insert(candidates, wildcard_base .. "-rsa")
insert(candidates, "customcert-" .. wildcard_base)
insert(candidates, "customcert-" .. wildcard_base .. "-ecdsa")
insert(candidates, "customcert-" .. wildcard_base .. "-rsa")
end
end
end
for _, name in ipairs(candidates) do
if name and name ~= "" then
local sanitized = sanitize_name(name)
local path = base_path .. sanitized .. "/ocsp.der"
local f = io.open(path, "rb")
if f then
local data = f:read("*a")
f:close()
if data and #data > 0 then
logger:log(INFO, "OCSP loaded response (" .. #data .. " bytes) from " .. path .. " (matches " .. name .. ")")
resp = data
-- Cache in shared memory to avoid disk I/O on every handshake (TTL 300s)
local ok_set, serr = internalstore:set(cache_key, resp, 300, true)
if not ok_set then
logger:log(ERR, "OCSP error while caching file response into internalstore: " .. (serr or "unknown"))
end
break
end
else
-- Check if file exists but couldn't be read (permission denied)
local file_exists = os.execute("test -f " .. path .. " 2>/dev/null")
if file_exists == 0 then
logger:log(ERR, "OCSP permission denied reading " .. path .. " (check file permissions)")
end
end
end
end
end
if not resp then
logger:log(ngx.DEBUG, "OCSP not found for " .. server_name)
return
end
local ok_set, oerr = ocsp.set_ocsp_status_resp(resp)
if not ok_set then
logger:log(ERR, "OCSP failed to set stapling: " .. oerr)
else
logger:log(INFO, "OCSP stapling set from cache for " .. server_name)
end
end
-- Call ssl_certificate() methods
logger:log(INFO, "calling ssl_certificate() methods of plugins ...")
for i, plugin_id in ipairs(phase_order) do
-- Require call
local plugin_lua, err = require_plugin(plugin_id)
if plugin_lua == false then
logger:log(ERR, err)
elseif plugin_lua == nil then
logger:log(INFO, err)
else
-- Check if plugin has ssl_certificate method
if plugin_lua.ssl_certificate ~= nil then
-- New call
local ok, plugin_obj = new_plugin(plugin_lua)
if not ok then
logger:log(ERR, plugin_obj)
else
local ok, ret = call_plugin(plugin_obj, "ssl_certificate")
if not ok then
logger:log(ERR, ret)
elseif not ret.ret then
logger:log(ERR, plugin_id .. ":ssl_certificate() call failed : " .. ret.msg)
else
logger:log(INFO, plugin_id .. ":ssl_certificate() call successful : " .. ret.msg)
if ret.status then
logger:log(INFO, plugin_id .. " is setting certificate/key : " .. ret.msg)
local ok, err = clear_certs()
if not ok then
logger:log(ERR, "error while clearing certificates : " .. err)
end
ok, err = set_cert(ret.status[1])
if not ok then
logger:log(ERR, "error while setting certificate : " .. err)
else
local ok, err = set_priv_key(ret.status[2])
if not ok then
logger:log(ERR, "error while setting private key : " .. err)
else
-- Try to set OCSP stapling from cache (if enabled)
logger:log(INFO, "DEBUG: About to call set_ocsp_from_cache() for " .. server_name)
set_ocsp_from_cache()
logger:log(INFO, "certificate set by " .. plugin_id)
return true
end
end
end
end
end
else
logger:log(INFO, "skipped execution of " .. plugin_id .. " because method ssl_certificate() is not defined")
end
end
end
logger:log(INFO, "called ssl_certificate() methods of plugins")
logger:log(INFO, "ssl_certificate phase ended")
return true
}