Make certbot compatible with 1.5

This commit is contained in:
TheophileDiot 2022-11-19 20:44:58 +01:00
parent aaeda53002
commit 4e96e57e05
6 changed files with 185 additions and 132 deletions

View File

@ -1,32 +1,46 @@
#!/usr/bin/python3
import sys, os, traceback
from os import getenv, makedirs
from os.path import exists
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
sys.path.append("/usr/share/bunkerweb/deps/python")
sys.path.append("/usr/share/bunkerweb/utils")
sys.path.append("/usr/share/bunkerweb/api")
sys_path.append("/usr/share/bunkerweb/deps/python")
sys_path.append("/usr/share/bunkerweb/utils")
sys_path.append("/usr/share/bunkerweb/api")
sys_path.append("/usr/share/bunkerweb/db")
from Database import Database
from logger import setup_logger
from API import API
logger = setup_logger("Lets-encrypt", os.getenv("LOG_LEVEL", "INFO"))
logger = setup_logger("Lets-encrypt", getenv("LOG_LEVEL", "INFO"))
db = Database(
logger,
sqlalchemy_string=getenv("DATABASE_URI", None),
)
status = 0
try:
# Get env vars
is_kubernetes_mode = os.getenv("KUBERNETES_MODE") == "yes"
is_swarm_mode = os.getenv("SWARM_MODE") == "yes"
is_autoconf_mode = os.getenv("AUTOCONF_MODE") == "yes"
token = os.getenv("CERTBOT_TOKEN")
validation = os.getenv("CERTBOT_VALIDATION")
bw_integration = None
if getenv("KUBERNETES_MODE") == "yes":
bw_integration = "Swarm"
elif getenv("SWARM_MODE") == "yes":
bw_integration = "Kubernetes"
elif getenv("AUTOCONF_MODE") == "yes":
bw_integration = "Autoconf"
elif exists("/usr/share/bunkerweb/INTEGRATION"):
with open("/usr/share/bunkerweb/INTEGRATION", "r") as f:
bw_integration = f.read().strip()
token = getenv("CERTBOT_TOKEN")
validation = getenv("CERTBOT_VALIDATION")
# Cluster case
if is_kubernetes_mode or is_swarm_mode or is_autoconf_mode:
for variable, value in os.environ.items():
if not variable.startswith("CLUSTER_INSTANCE_"):
continue
endpoint = value.split(" ")[0]
host = value.split(" ")[1]
if bw_integration in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
for instance in db.get_instances():
endpoint = f"http://{instance['hostname']}:{instance['port']}"
host = instance["server_name"]
api = API(endpoint, host=host)
sent, err, status, resp = api.request(
"POST",
@ -49,15 +63,14 @@ try:
f"Successfully sent API request to {api.get_endpoint()}/lets-encrypt/challenge",
)
# Docker or Linux case
# Linux case
else:
root_dir = "/var/tmp/bunkerweb/lets-encrypt/.well-known/acme-challenge/"
os.makedirs(root_dir, exist_ok=True)
with open(root_dir + token, "w") as f:
makedirs(root_dir, exist_ok=True)
with open(f"{root_dir}{token}", "w") as f:
f.write(validation)
except:
status = 1
logger.error("Exception while running certbot-auth.py :")
print(traceback.format_exc())
logger.error(f"Exception while running certbot-auth.py :\n{format_exc()}")
sys.exit(status)
sys_exit(status)

View File

@ -1,31 +1,45 @@
#!/usr/bin/python3
import sys, os, traceback
from os import getenv, remove
from os.path import exists, isfile
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
sys.path.append("/usr/share/bunkerweb/deps/python")
sys.path.append("/usr/share/bunkerweb/utils")
sys.path.append("/usr/share/bunkerweb/api")
sys_path.append("/usr/share/bunkerweb/deps/python")
sys_path.append("/usr/share/bunkerweb/utils")
sys_path.append("/usr/share/bunkerweb/api")
sys_path.append("/usr/share/bunkerweb/db")
from Database import Database
from logger import setup_logger
from API import API
logger = setup_logger("Lets-encrypt", os.getenv("LOG_LEVEL", "INFO"))
logger = setup_logger("Lets-encrypt", getenv("LOG_LEVEL", "INFO"))
db = Database(
logger,
sqlalchemy_string=getenv("DATABASE_URI", None),
)
status = 0
try:
# Get env vars
is_kubernetes_mode = os.getenv("KUBERNETES_MODE") == "yes"
is_swarm_mode = os.getenv("SWARM_MODE") == "yes"
is_autoconf_mode = os.getenv("AUTOCONF_MODE") == "yes"
token = os.getenv("CERTBOT_TOKEN")
bw_integration = None
if getenv("KUBERNETES_MODE") == "yes":
bw_integration = "Swarm"
elif getenv("SWARM_MODE") == "yes":
bw_integration = "Kubernetes"
elif getenv("AUTOCONF_MODE") == "yes":
bw_integration = "Autoconf"
elif exists("/usr/share/bunkerweb/INTEGRATION"):
with open("/usr/share/bunkerweb/INTEGRATION", "r") as f:
bw_integration = f.read().strip()
token = getenv("CERTBOT_TOKEN")
# Cluster case
if is_kubernetes_mode or is_swarm_mode or is_autoconf_mode:
for variable, value in os.environ.items():
if not variable.startswith("CLUSTER_INSTANCE_"):
continue
endpoint = value.split(" ")[0]
host = value.split(" ")[1]
if bw_integration in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
for instance in db.get_instances():
endpoint = f"http://{instance['hostname']}:{instance['port']}"
host = instance["server_name"]
api = API(endpoint, host=host)
sent, err, status, resp = api.request(
"DELETE", "/lets-encrypt/challenge", data={"token": token}
@ -45,17 +59,15 @@ try:
logger.info(
f"Successfully sent API request to {api.get_endpoint()}/lets-encrypt/challenge",
)
# Docker or Linux case
# Linux case
else:
challenge_path = (
f"/var/tmp/bunkerweb/lets-encrypt/.well-known/acme-challenge/{token}"
)
if os.path.isfile(challenge_path):
os.remove(challenge_path)
if isfile(challenge_path):
remove(challenge_path)
except:
status = 1
logger.error("Exception while running certbot-cleanup.py :")
print(traceback.format_exc())
logger.error(f"Exception while running certbot-cleanup.py :\n{format_exc()}")
sys.exit(status)
sys_exit(status)

View File

@ -1,7 +1,7 @@
#!/usr/bin/python3
from io import BytesIO
from os import environ, getenv
from os import getenv
from os.path import exists
from subprocess import run, DEVNULL, STDOUT
from sys import exit as sys_exit, path as sys_path
@ -11,13 +11,17 @@ from traceback import format_exc
sys_path.append("/usr/share/bunkerweb/deps/python")
sys_path.append("/usr/share/bunkerweb/utils")
sys_path.append("/usr/share/bunkerweb/api")
sys_path.append("/usr/share/bunkerweb/db")
from docker import DockerClient
from Database import Database
from logger import setup_logger
from API import API
logger = setup_logger("Lets-encrypt", getenv("LOG_LEVEL", "INFO"))
db = Database(
logger,
sqlalchemy_string=getenv("DATABASE_URI", None),
)
status = 0
try:
@ -33,11 +37,11 @@ try:
with open("/usr/share/bunkerweb/INTEGRATION", "r") as f:
bw_integration = f.read().strip()
token = getenv("CERTBOT_TOKEN")
logger.info(f"Certificates renewal for {getenv('RENEWED_DOMAINS')} successful")
# Cluster case
if bw_integration in ("Swarm", "Kubernetes", "Autoconf"):
if bw_integration in ("Docker", "Swarm", "Kubernetes", "Autoconf"):
# Create tarball of /data/cache/letsencrypt
tgz = BytesIO()
with tar_open(mode="w:gz", fileobj=tgz) as tf:
@ -45,11 +49,9 @@ try:
tgz.seek(0, 0)
files = {"archive.tar.gz": tgz}
for variable, value in environ.items():
if not variable.startswith("CLUSTER_INSTANCE_"):
continue
endpoint = value.split(" ")[0]
host = value.split(" ")[1]
for instance in db.get_instances():
endpoint = f"http://{instance['hostname']}:{instance['port']}"
host = instance["server_name"]
api = API(endpoint, host=host)
sent, err, status, resp = api.request(
"POST", "/lets-encrypt/certificates", files=files
@ -85,53 +87,13 @@ try:
logger.info(
f"Successfully sent API request to {api.get_endpoint()}/reload"
)
# Docker or Linux case
elif bw_integration == "Docker":
docker_client = DockerClient(
base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
# Linux case
else:
proc = run(
["nginx", "-s", "reload"],
stdin=DEVNULL,
stderr=STDOUT,
)
apis = []
for instance in docker_client.containers.list(
filters={"label": "bunkerweb.INSTANCE"}
):
api = None
for var in instance.attrs["Config"]["Env"]:
if var.startswith("API_HTTP_PORT="):
api = API(
f"http://{instance.name}:{var.replace('API_HTTP_PORT=', '', 1)}"
)
break
if api:
apis.append(api)
else:
apis.append(
API(f"http://{instance.name}:{getenv('API_HTTP_PORT', '5000')}")
)
for api in apis:
sent, err, status, resp = api.request("POST", "/reload")
if not sent:
status = 1
logger.error(
f"Can't send API request to {api.get_endpoint()}/reload : {err}"
)
else:
if status != 200:
status = 1
logger.error(
f"Error while sending API request to {api.get_endpoint()}/reload : status = {resp['status']}, msg = {resp['msg']}"
)
else:
logger.info(
f"Successfully sent API request to {api.get_endpoint()}/reload"
)
elif bw_integration == "Linux":
cmd = "/usr/sbin/nginx -s reload"
proc = run(cmd.split(" "), stdin=DEVNULL, stderr=STDOUT)
if proc.returncode != 0:
status = 1
logger.error("Error while reloading nginx")

View File

@ -1,6 +1,6 @@
#!/usr/bin/python3
from os import environ, getenv
from os import environ, getcwd, getenv
from os.path import exists
from subprocess import DEVNULL, STDOUT, run
from sys import exit as sys_exit, path as sys_path
@ -8,17 +8,42 @@ from traceback import format_exc
sys_path.append("/usr/share/bunkerweb/deps/python")
sys_path.append("/usr/share/bunkerweb/utils")
sys_path.append("/usr/share/bunkerweb/db")
from Database import Database
from logger import setup_logger
logger = setup_logger("LETS-ENCRYPT", getenv("LOG_LEVEL", "INFO"))
db = Database(
logger,
sqlalchemy_string=getenv("DATABASE_URI", None),
)
status = 0
def certbot_new(domains, email):
cmd = f"/usr/share/bunkerweb/deps/python/bin/certbot certonly --manual --preferred-challenges=http --manual-auth-hook /usr/share/bunkerweb/core/letsencrypt/jobs/certbot-auth.py --manual-cleanup-hook /usr/share/bunkerweb/core/letsencrypt/jobs/certbot-cleanup.py -n -d {domains} --email {email} --agree-tos"
if getenv("USE_LETS_ENCRYPT_STAGING") == "yes":
cmd += " --staging"
environ["PYTHONPATH"] = "/usr/share/bunkerweb/deps/python"
proc = run(
cmd.split(" "),
[
"/usr/share/bunkerweb/deps/python/bin/certbot certonly",
"--manual",
"--preferred-challenges=http",
"--manual-auth-hook",
f"{getcwd()}/certbot-auth.py",
"--manual-cleanup-hook",
f"{getcwd()}/certbot-cleanup.py",
"-n",
"-d",
domains,
"--email",
email,
"--agree-tos",
"--logs-dir",
"/var/tmp/bunkerweb",
"--work-dir",
"/var/lib/bunkerweb",
]
+ (["--staging"] if getenv("USE_LETS_ENCRYPT_STAGING", "no") == "yes" else []),
stdin=DEVNULL,
stderr=STDOUT,
env=environ,
@ -26,34 +51,37 @@ def certbot_new(domains, email):
return proc.returncode
logger = setup_logger("LETS-ENCRYPT", getenv("LOG_LEVEL", "INFO"))
status = 0
try:
# Multisite case
if getenv("MULTISITE") == "yes":
for first_server in getenv("SERVER_NAME").split(" "):
if getenv("MULTISITE", "no") == "yes":
for first_server in getenv("SERVER_NAME", "").split(" "):
if (
getenv(f"{first_server}_AUTO_LETS_ENCRYPT", getenv("AUTO_LETS_ENCRYPT"))
not first_server
or getenv(
f"{first_server}_AUTO_LETS_ENCRYPT",
getenv("AUTO_LETS_ENCRYPT", "no"),
)
!= "yes"
):
continue
if first_server == "":
continue
real_server_name = getenv(f"{first_server}_SERVER_NAME", first_server)
domains = real_server_name.replace(" ", ",")
domains = getenv(f"{first_server}_SERVER_NAME", first_server).replace(
" ", ","
)
if exists(f"/etc/letsencrypt/live/{first_server}/cert.pem"):
logger.info(
f"Certificates already exists for domain(s) {domains}",
)
continue
real_email = getenv(
f"{first_server}_EMAIL_LETS_ENCRYPT",
getenv("EMAIL_LETS_ENCRYPT", f"contact@{first_server}"),
)
if real_email == "":
if not real_email:
real_email = f"contact@{first_server}"
logger.info(
f"Asking certificates for domains : {domains} (email = {real_email}) ...",
)
@ -67,16 +95,32 @@ try:
f"Certificate generation succeeded for domain(s) : {domains}"
)
if exists(f"/etc/letsencrypt/live/{first_server}/cert.pem"):
with open(f"/etc/letsencrypt/live/{first_server}/cert.pem") as f:
cert = f.read()
# Update db
err = db.update_job_cache(
"letsencrypt",
first_server,
"cert.pem",
cert,
)
if err:
logger.warning(f"Couldn't update db cache: {err}")
# Singlesite case
elif getenv("AUTO_LETS_ENCRYPT") == "yes" and getenv("SERVER_NAME") != "":
first_server = getenv("SERVER_NAME").split(" ")[0]
domains = getenv("SERVER_NAME").replace(" ", ",")
elif getenv("AUTO_LETS_ENCRYPT", "no") == "yes" and getenv("SERVER_NAME", ""):
first_server = getenv("SERVER_NAME", "").split(" ")[0]
domains = getenv("SERVER_NAME", "").replace(" ", ",")
if exists(f"/etc/letsencrypt/live/{first_server}/cert.pem"):
logger.info(f"Certificates already exists for domain(s) {domains}")
else:
real_email = getenv("EMAIL_LETS_ENCRYPT", f"contact@{first_server}")
if real_email == "":
if not real_email:
real_email = f"contact@{first_server}"
logger.info(
f"Asking certificates for domain(s) : {domains} (email = {real_email}) ...",
)
@ -88,7 +132,19 @@ try:
f"Certificate generation succeeded for domain(s) : {domains}"
)
if exists(f"/etc/letsencrypt/live/{first_server}/cert.pem"):
with open(f"/etc/letsencrypt/live/{first_server}/cert.pem") as f:
cert = f.read()
# Update db
err = db.update_job_cache(
"letsencrypt",
first_server,
"cert.pem",
cert,
)
if err:
logger.warning(f"Couldn't update db cache: {err}")
except:
status = 1
logger.error(f"Exception while running certbot-new.py :\n{format_exc()}")

View File

@ -1,6 +1,6 @@
#!/usr/bin/python3
from os import environ, getenv
from os import environ, getcwd, getenv
from os.path import exists
from subprocess import DEVNULL, STDOUT, run
from sys import exit as sys_exit, path as sys_path
@ -13,10 +13,16 @@ from logger import setup_logger
def renew(domain):
cmd = f"/usr/share/bunkerweb/deps/python/bin/certbot renew --cert-name {domain} --deploy-hook /usr/share/bunkerweb/core/letsencrypt/jobs/certbot-deploy.py"
environ["PYTHONPATH"] = "/usr/share/bunkerweb/deps/python"
proc = run(
cmd.split(" "),
[
"/usr/share/bunkerweb/deps/python/bin/certbot",
"renew",
"--cert-name",
domain,
"--deploy-hook",
f"{getcwd()}/certbot-deploy.py",
],
stdin=DEVNULL,
stderr=STDOUT,
env=environ,
@ -30,23 +36,25 @@ status = 0
try:
if getenv("MULTISITE") == "yes":
for first_server in getenv("SERVER_NAME").split(" "):
if first_server == "":
continue
if (
getenv(f"{first_server}_AUTO_LETS_ENCRYPT", getenv("AUTO_LETS_ENCRYPT"))
not first_server
or getenv(
f"{first_server}_AUTO_LETS_ENCRYPT",
getenv("AUTO_LETS_ENCRYPT", "no"),
)
!= "yes"
or not exists(f"/etc/letsencrypt/live/{first_server}/cert.pem")
):
continue
if not exists(f"/etc/letsencrypt/live/{first_server}/cert.pem"):
continue
ret = renew(first_server)
if ret != 0:
status = 2
logger.error(
f"Certificates renewal for {first_server} failed",
)
elif getenv("AUTO_LETS_ENCRYPT") == "yes" and getenv("SERVER_NAME") != "":
first_server = getenv("SERVER_NAME").split(" ")[0]
elif getenv("AUTO_LETS_ENCRYPT", "no") == "yes" and not getenv("SERVER_NAME", ""):
first_server = getenv("SERVER_NAME", "").split(" ")[0]
if exists(f"/etc/letsencrypt/live/{first_server}/cert.pem"):
ret = renew(first_server)
if ret != 0:

View File

@ -80,7 +80,7 @@ class JobScheduler(ApiCaller):
if self.__integration == "Linux":
self.__logger.info("Reloading nginx ...")
proc = run(
["/usr/sbin/nginx", "-s", "reload"],
["nginx", "-s", "reload"],
stdin=DEVNULL,
stderr=PIPE,
env=self.__env,
@ -111,6 +111,8 @@ class JobScheduler(ApiCaller):
stdin=DEVNULL,
stderr=STDOUT,
env=self.__env,
user=101,
group=101,
)
except BaseException:
with self.__thread_lock: