bunkerweb 1.4.0

This commit is contained in:
bunkerity
2022-06-03 17:24:14 +02:00
parent 3a078326c5
commit a9f886804a
5245 changed files with 1432051 additions and 27894 deletions

View File

@@ -0,0 +1,4 @@
# set location for challenges
location ~ ^/.well-known/acme-challenge/ {
root /opt/bunkerweb/tmp/lets-encrypt;
}

View File

@@ -0,0 +1,24 @@
# set location for challenges
location ~ ^/.well-known/acme-challenge/ {
root /opt/bunkerweb/tmp/lets-encrypt;
}
{% if AUTO_LETS_ENCRYPT == "yes" %}
# listen on HTTPS PORT
listen 0.0.0.0:{{ HTTPS_PORT }} ssl {% if HTTP2 == "yes" %}http2{% endif %} {% if USE_PROXY_PROTOCOL == "yes" %}proxy_protocol{% endif %};
# TLS config
ssl_certificate /etc/letsencrypt/live/{{ SERVER_NAME.split(" ")[0] }}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/{{ SERVER_NAME.split(" ")[0] }}/privkey.pem;
ssl_protocols {{ HTTPS_PROTOCOLS }};
ssl_prefer_server_ciphers on;
ssl_session_tickets off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
{% if "TLSv1.2" in HTTPS_PROTOCOLS +%}
ssl_dhparam /etc/nginx/dhparam;
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 %}

View File

@@ -0,0 +1,51 @@
#!/usr/bin/python3
import sys, os, traceback
sys.path.append("/opt/bunkerweb/deps/python")
sys.path.append("/opt/bunkerweb/utils")
sys.path.append("/opt/bunkerweb/api")
from logger import log
from API import API
status = 0
try :
# Get env vars
is_kubernetes_mode = os.getenv("KUBERNETES_MODE") == "yes"
is_swarm_mode = os.getenv("SWARM_MODE") == "yes"
token = os.getenv("CERTBOT_TOKEN")
validation = os.getenv("CERTBOT_VALIDATION")
# Cluster case
if is_kubernetes_mode or is_swarm_mode :
for variable, value in os.environ.items() :
if not variable.startswith("CLUSTER_INSTANCE_") :
continue
endpoint = value.split(" ")[0]
host = value.split(" ")[1]
api = API(endpoint, host=host)
sent, err, status, resp = api.request("POST", "/lets-encrypt/challenge", data={"token": token, "validation": validation})
if not sent :
status = 1
log("LETS-ENCRYPT", "", "Can't send API request to " + api.get_endpoint() + "/lets-encrypt/challenge : " + err)
else :
if status != 200 :
status = 1
log("LETS-ENCRYPT", "", "Error while sending API request to " + api.get_endpoint() + "/lets-encrypt/challenge : status = " + resp["status"] + ", msg = " + resp["msg"])
else :
log("LETS-ENCRYPT", "", "Successfully sent API request to " + api.get_endpoint() + "/lets-encrypt/challenge")
# Docker or Linux case
else :
root_dir = "/opt/bunkerweb/tmp/lets-encrypt/.well-known/acme-challenge/"
os.makedirs(root_dir, exist_ok=True)
with open(root_dir + token, "w") as f :
f.write(validation)
except :
status = 1
log("LETS-ENCRYPT", "", "Exception while running certbot-auth.py :")
print(traceback.format_exc())
sys.exit(status)

View File

@@ -0,0 +1,49 @@
#!/usr/bin/python3
import sys, os, traceback
sys.path.append("/opt/bunkerweb/deps/python")
sys.path.append("/opt/bunkerweb/utils")
sys.path.append("/opt/bunkerweb/api")
from logger import log
from API import API
status = 0
try :
# Get env vars
is_kubernetes_mode = os.getenv("KUBERNETES_MODE") == "yes"
is_swarm_mode = os.getenv("SWARM_MODE") == "yes"
token = os.getenv("CERTBOT_TOKEN")
# Cluster case
if is_kubernetes_mode or is_swarm_mode :
for variable, value in os.environ.items() :
if not variable.startswith("CLUSTER_INSTANCE_") :
continue
endpoint = value.split(" ")[0]
host = value.split(" ")[1]
api = API(endpoint, host=host)
sent, err, status, resp = api.request("DELETE", "/lets-encrypt/challenge", data={"token": token})
if not sent :
status = 1
log("LETS-ENCRYPT", "", "Can't send API request to " + api.get_endpoint() + "/lets-encrypt/challenge : " + err)
else :
if status != 200 :
status = 1
log("LETS-ENCRYPT", "", "Error while sending API request to " + api.get_endpoint() + "/lets-encrypt/challenge : status = " + resp["status"] + ", msg = " + resp["msg"])
else :
log("LETS-ENCRYPT", "", "Successfully sent API request to " + api.get_endpoint() + "/lets-encrypt/challenge")
# Docker or Linux case
else :
challenge_path = "/opt/bunkerweb/tmp/lets-encrypt/.well-known/acme-challenge/" + token
if os.path.isfile(challenge_path) :
os.remove(challenge_path)
except :
status = 1
log("LETS-ENCRYPT", "", "Exception while running certbot-cleanup.py :")
print(traceback.format_exc())
sys.exit(status)

View File

@@ -0,0 +1,74 @@
#!/usr/bin/python3
import sys, os, traceback, tarfile
from io import BytesIO
sys.path.append("/opt/bunkerweb/deps/python")
sys.path.append("/opt/bunkerweb/utils")
sys.path.append("/opt/bunkerweb/api")
from logger import log
from API import API
status = 0
try :
# Get env vars
is_kubernetes_mode = os.getenv("KUBERNETES_MODE") == "yes"
is_swarm_mode = os.getenv("SWARM_MODE") == "yes"
token = os.getenv("CERTBOT_TOKEN")
# Cluster case
if is_kubernetes_mode or is_swarm_mode :
# Create tarball of /data/letsencrypt
tgz = BytesIO()
with tarfile.open(mode="w:gz", fileobj=tgz) as tf :
tf.add("/data/letsencrypt", arcname=".")
tgz.seek(0, 0)
files = {"archive.tar.gz": tgz}
for variable, value in os.environ.items() :
if not variable.startswith("CLUSTER_INSTANCE_") :
continue
endpoint = value.split(" ")[0]
host = value.split(" ")[1]
api = API(endpoint, host=host)
sent, err, status, resp = api.request("POST", "/lets-encrypt/certificates", files=files)
if not sent :
status = 1
log("LETS-ENCRYPT", "", "Can't send API request to " + api.get_endpoint() + "/lets-encrypt/certificates : " + err)
else :
if status != 200 :
status = 1
log("LETS-ENCRYPT", "", "Error while sending API request to " + api.get_endpoint() + "/lets-encrypt/certificates : status = " + resp["status"] + ", msg = " + resp["msg"])
else :
log("LETS-ENCRYPT", "", "Successfully sent API request to " + api.get_endpoint() + "/lets-encrypt/certificates")
sent, err, status, resp = api.request("POST", "/reload")
if not sent :
status = 1
log("LETS-ENCRYPT", "", "Can't send API request to " + api.get_endpoint() + "/reload : " + err)
else :
if status != 200 :
status = 1
log("LETS-ENCRYPT", "", "Error while sending API request to " + api.get_endpoint() + "/reload : status = " + resp["status"] + ", msg = " + resp["msg"])
else :
log("LETS-ENCRYPT", "", "Successfully sent API request to " + api.get_endpoint() + "/reload")
# Docker or Linux case
else :
cmd = "/usr/sbin/nginx -s reload"
proc = subprocess.run(cmd.split(" "), stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT)
if proc.returncode != 0 :
status = 1
log("LETS-ENCRYPT", "", "Error while reloading nginx")
else :
log("LETS-ENCRYPT", "", "Successfully reloaded nginx")
except :
status = 1
log("LETS-ENCRYPT", "", "Exception while running certbot-deploy.py :")
print(traceback.format_exc())
sys.exit(status)

View File

@@ -0,0 +1,66 @@
#!/usr/bin/python3
import sys, os, subprocess, traceback
sys.path.append("/opt/bunkerweb/deps/python")
sys.path.append("/opt/bunkerweb/utils")
import logger
def certbot_new(first_domain, domains, email) :
cmd = "/opt/bunkerweb/deps/python/bin/certbot certonly --manual --preferred-challenges=http --manual-auth-hook /opt/bunkerweb/core/letsencrypt/jobs/certbot-auth.py --manual-cleanup-hook /opt/bunkerweb/core/letsencrypt/jobs/certbot-cleanup.py -n -d " + domains + " --email " + email + " --agree-tos"
if os.getenv("USE_LETS_ENCRYPT_STAGING") == "yes" :
cmd += " --staging"
os.environ["PYTHONPATH"] = "/opt/bunkerweb/deps/python"
proc = subprocess.run(cmd.split(" "), stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT, env=os.environ)
return proc.returncode
status = 0
try :
# Multisite case
if os.getenv("MULTISITE") == "yes" :
for first_server in os.getenv("SERVER_NAME").split(" ") :
if os.getenv(first_server + "_AUTO_LETS_ENCRYPT", os.getenv("AUTO_LETS_ENCRYPT")) != "yes" :
continue
if first_server == "" :
continue
real_server_name = os.getenv(first_server + "_SERVER_NAME", first_server)
domains = real_server_name.replace(" ", ",")
if os.path.exists("/etc/letsencrypt/live/" + first_server + "/cert.pem") :
logger.log("LETS-ENCRYPT", "", "Certificates already exists for domain(s) " + domains)
continue
real_email = os.getenv(first_server + "_EMAIL_LETS_ENCRYPT", os.getenv("EMAIL_LETS_ENCRYPT", "contact@" + first_server))
if real_email == "" :
real_email = "contact@" + first_server
logger.log("LETS-ENCRYPT", "", "Asking certificates for domains : " + domains + " (email = " + real_email + ") ...")
if certbot_new(first_server, domains, real_email) != 0 :
status = 1
logger.log("LETS-ENCRYPT", "", "Certificate generation failed for domain(s) " + domains + " ...")
else :
logger.log("LETS-ENCRYPT", "", "Certificate generation succeeded for domain(s) : " + domains)
# Singlesite case
elif os.getenv("AUTO_LETS_ENCRYPT") == "yes" and os.getenv("SERVER_NAME") != "" :
first_server = os.getenv("SERVER_NAME").split(" ")[0]
domains = os.getenv("SERVER_NAME").replace(" ", ",")
if not os.path.exists("/etc/letsencrypt/live/" + first_server + "/cert.pem") :
logger.log("LETS-ENCRYPT", "", "Certificates already exists for domain(s) " + domains)
else :
real_email = os.getenv("EMAIL_LETS_ENCRYPT", "contact@" + first_server)
if real_email == "" :
real_email = "contact@" + first_server
logger.log("LETS-ENCRYPT", "", "Asking certificates for domain(s) : " + domains + " (email = " + real_email + ") ...")
if certbot_new(first_server, domains, real_email) != 0 :
status = 2
logger.log("LETS-ENCRYPT", "", "Certificate generation failed for domain(s) : " + domains)
else :
logger.log("LETS-ENCRYPT", "", "Certificate generation succeeded for domain(s) : " + domains)
except :
status = 1
logger.log("LETS-ENCRYPT", "", "Exception while running certbot-new.py :")
print(traceback.format_exc())
sys.exit(status)

View File

@@ -0,0 +1,50 @@
#!/usr/bin/python3
import sys, os, subprocess, traceback
sys.path.append("/opt/bunkerweb/deps/python")
sys.path.append("/opt/bunkerweb/utils")
import logger
def renew(domain) :
cmd = "/opt/bunkerweb/deps/python/bin/certbot renew --cert-name " + domain + " --deploy-hook /opt/bunkerweb/core/letsencrypt/jobs/certbot-deploy.py"
os.environ["PYTHONPATH"] = "/opt/bunkerweb/deps/python"
proc = subprocess.run(cmd.split(" "), stdin=subprocess.DEVNULL, stderr=subprocess.STDOUT, env=os.environ)
return proc.returncode
status = 0
try :
if os.getenv("MULTISITE") == "yes" :
for first_server in os.getenv("SERVER_NAME").split(" ") :
if first_server == "" :
continue
if os.getenv(first_server + "_AUTO_LETS_ENCRYPT", os.getenv("AUTO_LETS_ENCRYPT")) != "yes" :
continue
if not os.path.exists("/etc/letsencrypt/live/" + first_server + "/cert.pem") :
continue
ret = renew(first_server)
if ret != 0 :
status = 2
logger.log("LETS-ENCRYPT", "", "Certificates renewal for " + first_server + " failed")
else :
logger.log("LETS-ENCRYPT", "", "Certificates renewal for " + first_server + " successful")
elif os.getenv("AUTO_LETS_ENCRYPT") == "yes" and os.getenv("SERVER_NAME") != "" :
first_server = os.getenv("SERVER_NAME").split(" ")[0]
if os.path.exists("/etc/letsencrypt/live/" + first_server + "/cert.pem") :
ret = renew(first_server)
if ret != 0 :
status = 2
logger.log("LETS-ENCRYPT", "", "Certificates renewal for " + first_server + " failed")
else :
logger.log("LETS-ENCRYPT", "", "Certificates renewal for " + first_server + " successful")
except :
status = 2
logger.log("LETS-ENCRYPT", "", "Exception while running certbot-renew.py :")
print(traceback.format_exc())
sys.exit(status)

View File

@@ -0,0 +1,49 @@
local _M = {}
_M.__index = _M
local logger = require "logger"
local cjson = require "cjson"
function _M.new()
local self = setmetatable({}, _M)
return self, nil
end
function _M:access()
if string.sub(ngx.var.uri, 1, string.len("/.well-known/acme-challenge/")) == "/.well-known/acme-challenge/" then
logger.log(ngx.NOTICE, "LETS-ENCRYPT", "Got a visit from Let's Encrypt, let's whitelist it.")
return true, "success", true, ngx.exit(ngx.OK)
end
return true, "success", false, nil
end
function _M:api()
if not string.match(ngx.var.uri, "^/lets%-encrypt/challenge$") or (ngx.var.request_method ~= "POST" and ngx.var.request_method ~= "DELETE") then
return false, nil, nil
end
local acme_folder = "/opt/bunkerweb/tmp/lets-encrypt/.well-known/acme-challenge/"
ngx.req.read_body()
local ret, data = pcall(cjson.decode, ngx.req.get_body_data())
if not ret then
return true, ngx.HTTP_BAD_REQUEST, {status = "error", msg = "json body decoding failed"}
end
os.execute("mkdir -p " .. acme_folder)
if ngx.var.request_method == "POST" then
local file, err = io.open(acme_folder .. data.token, "w+")
if not file then
return true, ngx.HTTP_INTERNAL_SERVER_ERROR, {status = "error", msg = "can't write validation token : " .. err}
end
file:write(data.validation)
file:close()
return true, ngx.HTTP_OK, {status = "success", msg = "validation token written"}
elseif ngx.var.request_method == "DELETE" then
local ok, err = os.remove(acme_folder .. data.token)
if not ok then
return true, ngx.HTTP_INTERNAL_SERVER_ERROR, {status = "error", msg = "can't remove validation token : " .. err}
end
return true, ngx.HTTP_OK, {status = "success", msg = "validation token removed"}
end
return true, ngx.HTTP_NOT_FOUND, {status = "error", msg = "unknown request"}
end
return _M

View File

@@ -0,0 +1,50 @@
{
"id": "letsencrypt",
"order": 1,
"name": "Let's Encrypt",
"description": "Automatic creation, renewal and configuration of Let's Encrypt certificates.",
"version": "0.1",
"settings": {
"AUTO_LETS_ENCRYPT": {
"context": "multisite",
"default": "no",
"help": "Activate automatic Let's Encrypt mode.",
"id": "auto-lets-encrypt",
"label": "Automatic Let's Encrypt",
"regex": "^(yes|no)$",
"type": "check"
},
"EMAIL_LETS_ENCRYPT": {
"context": "multisite",
"default": "",
"help": "Email used for Let's Encrypt notification and in certificate.",
"id": "email-lets-encrypt",
"label": "Email Let's Encrypt",
"regex": "^.*$",
"type": "text"
},
"USE_LETS_ENCRYPT_STAGING": {
"context": "multisite",
"default": "no",
"help": "Use the staging environment for Lets Encrypt certificate generation. Useful when you are testing your deployments to avoid being rate limited in the production environment.",
"id": "use-lets-encrypt-staging",
"label": "Use Let's Encrypt Staging",
"regex": "^(yes|no)$",
"type": "check"
}
},
"jobs": [
{
"name": "certbot-new",
"file": "certbot-new.py",
"every": "once",
"reload": false
},
{
"name": "certbot-renew",
"file": "certbot-renew.py",
"every": "day",
"reload": true
}
]
}