Refactor acme.sh integration

This commit is contained in:
VirtuBox
2019-09-22 19:30:39 +02:00
parent c6b081868f
commit e817a4086c
4 changed files with 258 additions and 217 deletions

View File

@@ -10,11 +10,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
#### Changed #### Changed
- [APP] WordOps dashboard updated to v1.2. Shipped as a html file, it can be used without PHP stack - [APP] WordOps dashboard updated to v1.2, shipped as a html file, it can be used without PHP stack
#### Fixed #### Fixed
- `wo stack purge --all` failure if mysql isn't installed - `wo stack purge --all` failure if mysql isn't installed
- Fix EEv3 files cleanup
### v3.9.8.12 - 2019-09-20 ### v3.9.8.12 - 2019-09-20

View File

@@ -1,9 +1,7 @@
# """WordOps site controller."""
import glob import glob
import json import json
import os import os
import subprocess import subprocess
from subprocess import Popen
from cement.core import handler, hook from cement.core import handler, hook
from cement.core.controller import CementBaseController, expose from cement.core.controller import CementBaseController, expose
@@ -11,6 +9,7 @@ from cement.core.controller import CementBaseController, expose
from wo.cli.plugins.site_functions import * from wo.cli.plugins.site_functions import *
from wo.cli.plugins.sitedb import (addNewSite, deleteSiteInfo, getAllsites, from wo.cli.plugins.sitedb import (addNewSite, deleteSiteInfo, getAllsites,
getSiteInfo, updateSiteInfo) getSiteInfo, updateSiteInfo)
from wo.core.acme import WOAcme
from wo.core.domainvalidate import WODomain from wo.core.domainvalidate import WODomain
from wo.core.fileutils import WOFileUtils from wo.core.fileutils import WOFileUtils
from wo.core.git import WOGit from wo.core.git import WOGit
@@ -58,7 +57,8 @@ class WOSiteController(CementBaseController):
pargs.site_name = pargs.site_name.strip() pargs.site_name = pargs.site_name.strip()
# validate domain name # validate domain name
(wo_domain, wo_www_domain) = WODomain.validatedomain(self, pargs.site_name) (wo_domain,
wo_www_domain) = WODomain.validatedomain(self, pargs.site_name)
# check if site exists # check if site exists
if not check_domain_exists(self, wo_domain): if not check_domain_exists(self, wo_domain):
@@ -136,8 +136,10 @@ class WOSiteController(CementBaseController):
Log.debug(self, str(e)) Log.debug(self, str(e))
Log.error(self, 'could not input site name') Log.error(self, 'could not input site name')
pargs.site_name = pargs.site_name.strip() pargs.site_name = pargs.site_name.strip()
(wo_domain, wo_www_domain) = WODomain.validatedomain(self, pargs.site_name) (wo_domain,
(wo_domain_type, wo_root_domain) = WODomain.getdomainlevel(self, wo_domain) wo_www_domain) = WODomain.validatedomain(self, pargs.site_name)
(wo_domain_type,
wo_root_domain) = WODomain.getdomainlevel(self, wo_domain)
wo_db_name = '' wo_db_name = ''
wo_db_user = '' wo_db_user = ''
wo_db_pass = '' wo_db_pass = ''
@@ -188,7 +190,8 @@ class WOSiteController(CementBaseController):
def log(self): def log(self):
pargs = self.app.pargs pargs = self.app.pargs
pargs.site_name = pargs.site_name.strip() pargs.site_name = pargs.site_name.strip()
(wo_domain, wo_www_domain) = WODomain.validatedomain(self, pargs.site_name) (wo_domain,
wo_www_domain) = WODomain.validatedomain(self, pargs.site_name)
wo_site_webroot = getSiteInfo(self, wo_domain).site_path wo_site_webroot = getSiteInfo(self, wo_domain).site_path
if not check_domain_exists(self, wo_domain): if not check_domain_exists(self, wo_domain):
@@ -210,7 +213,8 @@ class WOSiteController(CementBaseController):
Log.error(self, 'could not input site name') Log.error(self, 'could not input site name')
# TODO Write code for wo site edit command here # TODO Write code for wo site edit command here
pargs.site_name = pargs.site_name.strip() pargs.site_name = pargs.site_name.strip()
(wo_domain, wo_www_domain) = WODomain.validatedomain(self, pargs.site_name) (wo_domain,
wo_www_domain) = WODomain.validatedomain(self, pargs.site_name)
if not check_domain_exists(self, wo_domain): if not check_domain_exists(self, wo_domain):
Log.error(self, "site {0} does not exist".format(wo_domain)) Log.error(self, "site {0} does not exist".format(wo_domain))
@@ -241,7 +245,8 @@ class WOSiteController(CementBaseController):
Log.error(self, 'Unable to read input, please try again') Log.error(self, 'Unable to read input, please try again')
pargs.site_name = pargs.site_name.strip() pargs.site_name = pargs.site_name.strip()
(wo_domain, wo_www_domain) = WODomain.validatedomain(self, pargs.site_name) (wo_domain,
wo_www_domain) = WODomain.validatedomain(self, pargs.site_name)
if not check_domain_exists(self, wo_domain): if not check_domain_exists(self, wo_domain):
Log.error(self, "site {0} does not exist".format(wo_domain)) Log.error(self, "site {0} does not exist".format(wo_domain))
@@ -281,7 +286,8 @@ class WOSiteEditController(CementBaseController):
Log.error(self, 'Unable to read input, Please try again') Log.error(self, 'Unable to read input, Please try again')
pargs.site_name = pargs.site_name.strip() pargs.site_name = pargs.site_name.strip()
(wo_domain, wo_www_domain) = WODomain.validatedomain(self, pargs.site_name) (wo_domain,
wo_www_domain) = WODomain.validatedomain(self, pargs.site_name)
if not check_domain_exists(self, wo_domain): if not check_domain_exists(self, wo_domain):
Log.error(self, "site {0} does not exist".format(wo_domain)) Log.error(self, "site {0} does not exist".format(wo_domain))
@@ -422,7 +428,8 @@ class WOSiteCreateController(CementBaseController):
Log.error(self, "Unable to input site name, Please try again!") Log.error(self, "Unable to input site name, Please try again!")
pargs.site_name = pargs.site_name.strip() pargs.site_name = pargs.site_name.strip()
(wo_domain, wo_www_domain) = WODomain.validatedomain(self, pargs.site_name) (wo_domain,
wo_www_domain) = WODomain.validatedomain(self, pargs.site_name)
if not wo_domain.strip(): if not wo_domain.strip():
Log.error(self, "Invalid domain name, " Log.error(self, "Invalid domain name, "
"Provide valid domain name") "Provide valid domain name")
@@ -715,31 +722,52 @@ class WOSiteCreateController(CementBaseController):
"`tail /var/log/wo/wordops.log` and please try again") "`tail /var/log/wo/wordops.log` and please try again")
if pargs.letsencrypt: if pargs.letsencrypt:
(wo_domain_type, wo_root_domain) = WODomain.getdomainlevel(self, acme_domains = []
wo_domain) (wo_domain_type,
wo_root_domain) = WODomain.getdomainlevel(self,
wo_domain)
data['letsencrypt'] = True data['letsencrypt'] = True
letsencrypt = True letsencrypt = True
if data['letsencrypt'] is True: if data['letsencrypt'] is True:
Log.debug(self, "Going to issue Let's Encrypt certificate")
acmedata = dict(acme_domains, dns=False, acme_dns='dns_cf')
if pargs.dns: if pargs.dns:
wo_acme_dns = pargs.dns Log.debug(self, "DNS validation enabled")
wo_dns = True acmedata['dns'] = True
else: if not pargs.dns == 'dns_cf':
wo_acme_dns = '' Log.debug(self, "DNS API : {0}".format(pargs.dns))
wo_dns = False acmedata['acme_dns'] = pargs.dns
# detect subdomain and set subdomain variable
if pargs.letsencrypt == "subdomain": if pargs.letsencrypt == "subdomain":
wo_subdomain = True Log.warn(
wo_wildcard = False self, 'Flag --letsencrypt=subdomain is '
'deprecated and not required anymore.')
acme_subdomain = True
acme_wildcard = False
elif pargs.letsencrypt == "wildcard": elif pargs.letsencrypt == "wildcard":
wo_wildcard = True acme_wildcard = True
wo_subdomain = False acme_subdomain = False
acmedata['dns'] = True
else: else:
wo_wildcard = False if ((wo_domain_type == 'subdomain')):
wo_subdomain = False Log.debug(self, "Domain type = {0}"
Log.debug(self, "Domain type = {0}" .format(wo_domain_type))
.format(wo_domain_type)) acme_subdomain = True
if ((wo_domain_type == 'subdomain') and else:
(not pargs.letsencrypt == 'wildcard')): acme_subdomain = False
wo_subdomain = True acme_wildcard = False
if acme_subdomain is True:
acme_domains = acme_domains + ['{0}'.format(wo_domain)]
elif acme_wildcard is True:
acme_domains = acme_domains + ['{0}'.format(wo_domain),
'*.{0}'.format(wo_domain)]
else:
acme_domains = acme_domains + ['{0}'.format(wo_domain),
'www.{0}'.format(wo_domain)]
if acme_subdomain is True:
# check if a wildcard cert for the root domain exist # check if a wildcard cert for the root domain exist
Log.debug(self, "checkWildcardExist on *.{0}" Log.debug(self, "checkWildcardExist on *.{0}"
.format(wo_root_domain)) .format(wo_root_domain))
@@ -757,12 +785,15 @@ class WOSiteCreateController(CementBaseController):
else: else:
Log.debug(self, "Setup Cert with acme.sh for {0}" Log.debug(self, "Setup Cert with acme.sh for {0}"
.format(wo_domain)) .format(wo_domain))
setupLetsEncrypt(self, wo_domain, wo_subdomain, Log.info(self, "Certificate type: Subdomain")
wo_wildcard, wo_dns, wo_acme_dns) if WOAcme.setupletsencrypt(
self, acme_domains, acmedata):
WOAcme.deploycert(self, wo_domain)
else: else:
setupLetsEncrypt(self, wo_domain, wo_subdomain, if WOAcme.setupletsencrypt(
wo_wildcard, wo_dns, wo_acme_dns) self, acme_domains, acmedata):
httpsRedirect(self, wo_domain, True, wo_wildcard) WOAcme.deploycert(self, wo_domain)
httpsRedirect(self, wo_domain, True, acme_wildcard)
if pargs.hsts: if pargs.hsts:
SSL.setuphsts(self, wo_domain) SSL.setuphsts(self, wo_domain)
@@ -928,7 +959,8 @@ class WOSiteUpdateController(CementBaseController):
Log.error(self, 'Unable to input site name, Please try again!') Log.error(self, 'Unable to input site name, Please try again!')
pargs.site_name = pargs.site_name.strip() pargs.site_name = pargs.site_name.strip()
(wo_domain, wo_www_domain) = WODomain.validatedomain(self, pargs.site_name) (wo_domain,
wo_www_domain) = WODomain.validatedomain(self, pargs.site_name)
wo_site_webroot = WOVariables.wo_webroot + wo_domain wo_site_webroot = WOVariables.wo_webroot + wo_domain
check_site = getSiteInfo(self, wo_domain) check_site = getSiteInfo(self, wo_domain)
@@ -1126,44 +1158,47 @@ class WOSiteUpdateController(CementBaseController):
pargs.php73 = False pargs.php73 = False
if pargs.letsencrypt: if pargs.letsencrypt:
(wo_domain_type, wo_root_domain) = WODomain.getdomainlevel(self, acme_domains = []
wo_domain) acmedata = dict(acme_domains, dns=False, acme_dns='dns_cf')
(wo_domain_type,
wo_root_domain) = WODomain.getdomainlevel(self, wo_domain)
if pargs.letsencrypt == 'on': if pargs.letsencrypt == 'on':
data['letsencrypt'] = True data['letsencrypt'] = True
letsencrypt = True letsencrypt = True
if ((wo_domain_type == 'subdomain') and if (wo_domain_type == 'subdomain'):
(not pargs.letsencrypt == 'wildcard')): acme_subdomain = True
wo_subdomain = True
else: else:
wo_subdomain = False acme_subdomain = False
wo_wildcard = False acme_wildcard = False
elif pargs.letsencrypt == 'subdomain': elif pargs.letsencrypt == 'subdomain':
data['letsencrypt'] = True data['letsencrypt'] = True
letsencrypt = True letsencrypt = True
wo_subdomain = True acme_subdomain = True
wo_wildcard = False acme_wildcard = False
elif pargs.letsencrypt == 'wildcard': elif pargs.letsencrypt == 'wildcard':
data['letsencrypt'] = True data['letsencrypt'] = True
letsencrypt = True letsencrypt = True
wo_wildcard = True acme_wildcard = True
wo_subdomain = False acme_subdomain = False
acmedata['dns'] = True
elif pargs.letsencrypt == 'off': elif pargs.letsencrypt == 'off':
data['letsencrypt'] = False data['letsencrypt'] = False
letsencrypt = False letsencrypt = False
wo_subdomain = False acme_subdomain = False
wo_wildcard = False acme_wildcard = False
elif pargs.letsencrypt == 'clean': elif pargs.letsencrypt == 'clean':
data['letsencrypt'] = False data['letsencrypt'] = False
letsencrypt = False letsencrypt = False
wo_subdomain = False acme_subdomain = False
wo_wildcard = False acme_wildcard = False
elif pargs.letsencrypt == 'purge': elif pargs.letsencrypt == 'purge':
data['letsencrypt'] = False data['letsencrypt'] = False
letsencrypt = False letsencrypt = False
wo_subdomain = False acme_subdomain = False
wo_wildcard = False acme_wildcard = False
if not wo_subdomain: if not (acme_subdomain is True):
if letsencrypt is check_ssl: if letsencrypt is check_ssl:
if letsencrypt is False: if letsencrypt is False:
Log.error(self, "SSl is not configured for given " Log.error(self, "SSl is not configured for given "
@@ -1268,13 +1303,12 @@ class WOSiteUpdateController(CementBaseController):
data['php73'] = False data['php73'] = False
php73 = False php73 = False
if pargs.letsencrypt == "on" or pargs.php73 == "on": if pargs.php73 == "on":
if pargs.php73 == "on": data['php73'] = True
data['php73'] = True php73 = True
php73 = True else:
else: data['php73'] = False
data['php73'] = False php73 = False
php73 = False
if pargs.wpredis and data['currcachetype'] != 'wpredis': if pargs.wpredis and data['currcachetype'] != 'wpredis':
data['wpredis'] = True data['wpredis'] = True
@@ -1345,20 +1379,31 @@ class WOSiteUpdateController(CementBaseController):
if pargs.letsencrypt: if pargs.letsencrypt:
if data['letsencrypt'] is True: if data['letsencrypt'] is True:
# DNS API configuration
if pargs.dns: if pargs.dns:
wo_acme_dns = pargs.dns Log.debug(self, "DNS validation enabled")
wo_dns = True acmedata['dns'] = True
if not pargs.dns == 'dns_cf':
Log.debug(self, "DNS API : {0}".format(pargs.dns))
acmedata['acme_dns'] = pargs.dns
# Set list of domains to secure
if acme_subdomain is True:
acme_domains = acme_domains + ['{0}'.format(wo_domain)]
elif acme_wildcard is True:
acme_domains = acme_domains + ['{0}'.format(wo_domain),
'*.{0}'.format(wo_domain)]
else: else:
wo_acme_dns = '' acme_domains = acme_domains + ['{0}'.format(wo_domain),
wo_dns = False 'www.{0}'.format(wo_domain)]
if wo_subdomain:
if acme_subdomain:
# check if a wildcard cert for the root domain exist # check if a wildcard cert for the root domain exist
Log.debug(self, "checkWildcardExist on *.{0}" Log.debug(self, "checkWildcardExist on *.{0}"
.format(wo_root_domain)) .format(wo_root_domain))
iswildcard = SSL.checkwildcardexist(self, wo_root_domain) iswildcard = SSL.checkwildcardexist(self, wo_root_domain)
Log.debug(self, "iswildcard = {0}".format(iswildcard)) Log.debug(self, "iswildcard = {0}".format(iswildcard))
if not os.path.isfile("{0}/conf/nginx/ssl.conf.disabled"): if not os.path.isfile("{0}/conf/nginx/ssl.conf.disabled"):
if wo_subdomain: if acme_subdomain:
if iswildcard: if iswildcard:
Log.info(self, "Using existing Wildcard SSL " Log.info(self, "Using existing Wildcard SSL "
"certificate from {0} to secure {1}" "certificate from {0} to secure {1}"
@@ -1371,11 +1416,10 @@ class WOSiteUpdateController(CementBaseController):
else: else:
Log.debug(self, "Setup Cert with acme.sh for {0}" Log.debug(self, "Setup Cert with acme.sh for {0}"
.format(wo_domain)) .format(wo_domain))
setupLetsEncrypt(self, wo_domain, wo_subdomain, WOAcme.setupletsencrypt(
wo_wildcard, wo_dns, wo_acme_dns) self, acme_domains, acmedata)
else: else:
setupLetsEncrypt(self, wo_domain, wo_subdomain, WOAcme.setupletsencrypt(self, acme_domains, acmedata)
wo_wildcard, wo_dns, wo_acme_dns)
else: else:
WOFileUtils.mvfile(self, "{0}/conf/nginx/ssl.conf.disabled" WOFileUtils.mvfile(self, "{0}/conf/nginx/ssl.conf.disabled"
.format(wo_site_webroot), .format(wo_site_webroot),
@@ -1387,7 +1431,7 @@ class WOSiteUpdateController(CementBaseController):
'/etc/nginx/conf.d/force-ssl-{0}.conf' '/etc/nginx/conf.d/force-ssl-{0}.conf'
.format(wo_domain)) .format(wo_domain))
httpsRedirect(self, wo_domain, True, wo_wildcard) httpsRedirect(self, wo_domain, True, acme_wildcard)
SSL.siteurlhttps(self, wo_domain) SSL.siteurlhttps(self, wo_domain)
if not WOService.reload_service(self, 'nginx'): if not WOService.reload_service(self, 'nginx'):
@@ -1396,7 +1440,7 @@ class WOSiteUpdateController(CementBaseController):
Log.info(self, "Congratulations! Successfully " Log.info(self, "Congratulations! Successfully "
"Configured SSL for Site " "Configured SSL for Site "
" https://{0}".format(wo_domain)) " https://{0}".format(wo_domain))
if wo_subdomain and iswildcard: if acme_subdomain and iswildcard:
if (SSL.getexpirationdays(self, wo_root_domain) > 0): if (SSL.getexpirationdays(self, wo_root_domain) > 0):
Log.info( Log.info(
self, "Your cert will expire within " + self, "Your cert will expire within " +

View File

@@ -20,6 +20,7 @@ from wo.core.services import WOService
from wo.core.shellexec import CommandExecutionError, WOShellExec from wo.core.shellexec import CommandExecutionError, WOShellExec
from wo.core.sslutils import SSL from wo.core.sslutils import SSL
from wo.core.variables import WOVariables from wo.core.variables import WOVariables
from wo.core.acme import WOAcme
class SiteError(Exception): class SiteError(Exception):
@@ -1348,129 +1349,6 @@ def doCleanupAction(self, domain='', webroot='', dbname='', dbuser='',
# setup letsencrypt for domain + www.domain # setup letsencrypt for domain + www.domain
def setupLetsEncrypt(self, wo_domain_name, subdomain=False, wildcard=False,
wo_dns=False, wo_acme_dns='dns_cf'):
if os.path.isfile("/etc/letsencrypt/"
"renewal/{0}_ecc/"
"fullchain.cer".format(wo_domain_name)):
Log.debug(self, "Let's Encrypt certificate "
"found for the domain: {0}"
.format(wo_domain_name))
ssl = archivedCertificateHandle(self, wo_domain_name)
else:
keylenght = "{0}".format(self.app.config.get('letsencrypt',
'keylength'))
wo_acme_exec = ("/etc/letsencrypt/acme.sh --config-home "
"'/etc/letsencrypt/config'")
if wo_dns:
acme_mode = "--dns {0}".format(wo_acme_dns)
validation_mode = "DNS with {0}".format(wo_acme_dns)
Log.debug(
self, "Validation : DNS mode with {0}".format(wo_acme_dns))
else:
acme_mode = "-w /var/www/html"
validation_mode = "Webroot challenge"
Log.debug(self, "Validation : Webroot mode")
if subdomain:
Log.info(self, "Certificate type: Subdomain")
Log.info(self, "Validation mode : {0}".format(validation_mode))
Log.wait(self, "Issuing SSL cert with acme.sh")
ssl = WOShellExec.cmd_exec(self, "{0} ".format(wo_acme_exec) +
"--issue "
"-d {0} {1} "
"-k {2} -f"
.format(wo_domain_name,
acme_mode,
keylenght))
elif wildcard:
Log.info(self, "Certificate type: Wildcard")
Log.info(self, "Validation mode : {0}".format(validation_mode))
Log.wait(self, "Issuing SSL cert with acme.sh")
ssl = WOShellExec.cmd_exec(self, "{0} ".format(wo_acme_exec) +
"--issue "
"-d {0} -d '*.{0}' --dns {1} "
"-k {2} -f"
.format(wo_domain_name,
wo_acme_dns,
keylenght))
else:
Log.info(self, "Certificate type: Domain + www")
Log.info(self, "Validation mode : {0}".format(validation_mode))
Log.wait(self, "Issuing SSL cert with acme.sh")
ssl = WOShellExec.cmd_exec(self, "{0} ".format(wo_acme_exec) +
"--issue "
"-d {0} -d www.{0} {1} "
"-k {2} -f"
.format(wo_domain_name,
acme_mode, keylenght))
if ssl:
Log.valide(self, "Issuing SSL cert with acme.sh")
Log.wait(self, "Deploying SSL cert")
Log.debug(self, "Cert deployment for domain: {0}"
.format(wo_domain_name))
try:
WOShellExec.cmd_exec(self, "mkdir -p {0}/{1} && "
"/etc/letsencrypt/acme.sh "
"--config-home "
"'/etc/letsencrypt/config' "
"--install-cert -d {1} --ecc "
"--cert-file {0}/{1}/cert.pem "
"--key-file {0}/{1}/key.pem "
"--fullchain-file "
"{0}/{1}/fullchain.pem "
"--ca-file {0}/{1}/ca.pem "
"--reloadcmd "
"\"nginx -t && "
"service nginx restart\" "
.format(WOVariables.wo_ssl_live,
wo_domain_name))
Log.valide(self, "Deploying SSL cert")
if os.path.isdir('/var/www/{0}/conf/nginx'
.format(wo_domain_name)):
sslconf = open("/var/www/{0}/conf/nginx/ssl.conf"
.format(wo_domain_name),
encoding='utf-8', mode='w')
sslconf.write(
"listen 443 ssl http2;\n"
"listen [::]:443 ssl http2;\n"
"ssl_certificate {0}/{1}/fullchain.pem;\n"
"ssl_certificate_key {0}/{1}/key.pem;\n"
"ssl_trusted_certificate {0}/{1}/ca.pem;\n"
"ssl_stapling_verify on;\n"
.format(WOVariables.wo_ssl_live, wo_domain_name))
sslconf.close()
# updateSiteInfo(self, wo_domain_name, ssl=True)
if not WOFileUtils.grep(self, '/var/www/22222/conf/nginx/ssl.conf',
'/etc/letsencrypt'):
Log.info(self, "Securing WordOps backend with {0} certificate"
.format(wo_domain_name))
sslconf = open("/var/www/22222/conf/nginx/ssl.conf",
encoding='utf-8', mode='w')
sslconf.write("ssl_certificate {0}/{1}/fullchain.pem;\n"
"ssl_certificate_key {0}/{1}/key.pem;\n"
"ssl_trusted_certificate {0}/{1}/ca.pem;\n"
"ssl_stapling_verify on;\n"
.format(WOVariables.wo_ssl_live, wo_domain_name))
sslconf.close()
WOGit.add(self, ["/etc/letsencrypt"],
msg="Adding letsencrypt folder")
except IOError as e:
Log.debug(self, str(e))
Log.debug(self, "Error occured while generating "
"ssl.conf")
else:
Log.error(self, "Unable to install certificate", False)
Log.error(self, "Please make sure that your site is pointed to \n"
"same server on which "
"you are running Let\'s Encrypt Client "
"\n to allow it to verify the site automatically.")
# copy wildcard certificate to a subdomain # copy wildcard certificate to a subdomain
@@ -1616,14 +1494,16 @@ def httpsRedirect(self, wo_domain_name, redirect=True, wildcard=False):
def archivedCertificateHandle(self, domain): def archivedCertificateHandle(self, domain):
Log.warn(self, "You already have an existing certificate " Log.warn(
"for the domain requested.\n" self, "You already have an existing certificate "
"(ref: {0}/" "for the domain requested.\n"
"{1}_ecc/{1}.conf)".format(WOVariables.wo_ssl_archive, domain) + "(ref: {0}/"
"\nPlease select an option from below?" "{1}_ecc/{1}.conf)".format(WOVariables.wo_ssl_archive, domain) +
"\n\t1: Reinstall existing certificate" "\nPlease select an option from below?"
"\n\t2: Renew & replace the certificate (limit ~5 per 7 days)" "\n\t1: Reinstall existing certificate"
"") "\n\t2: Issue a new certificate to replace "
"the current one (limit ~5 per 7 days)"
"")
check_prompt = input( check_prompt = input(
"\nType the appropriate number [1-2] or any other key to cancel: ") "\nType the appropriate number [1-2] or any other key to cancel: ")
if not os.path.isfile("{0}/{1}/fullchain.pem" if not os.path.isfile("{0}/{1}/fullchain.pem"
@@ -1634,20 +1514,7 @@ def archivedCertificateHandle(self, domain):
if check_prompt == "1": if check_prompt == "1":
Log.info(self, "Reinstalling SSL cert with acme.sh") Log.info(self, "Reinstalling SSL cert with acme.sh")
ssl = WOShellExec.cmd_exec(self, "mkdir -p {0}/{1} && " ssl = WOAcme.deploycert(self, domain)
"/etc/letsencrypt/acme.sh "
"--config-home "
"'/etc/letsencrypt/config' "
"--install-cert -d {1} --ecc "
"--cert-file {0}/{1}/cert.pem "
"--key-file {0}/{1}/key.pem "
"--fullchain-file "
"{0}/{1}/fullchain.pem "
"--ca-file {0}/{1}/ca.pem "
"--reloadcmd "
"\"nginx -t && service nginx restart\" "
.format(WOVariables.wo_ssl_live,
domain))
if ssl: if ssl:
try: try:

129
wo/core/acme.py Normal file
View File

@@ -0,0 +1,129 @@
import os
import requests
from wo.core.fileutils import WOFileUtils
from wo.core.git import WOGit
from wo.core.logging import Log
from wo.core.shellexec import WOShellExec
from wo.core.variables import WOVariables
class WOAcme:
"""Acme.sh utilities for WordOps"""
def setupletsencrypt(self, acme_domain, acmedata):
"""issue SSL certificates with acme.sh"""
all_domains = '\' -d \''.join(acme_domain)
wo_acme_dns = acmedata['acme_dns']
keylenght = "{0}".format(self.app.config.get('letsencrypt',
'keylength'))
wo_acme_exec = ("/etc/letsencrypt/acme.sh --config-home "
"'/etc/letsencrypt/config'")
if acmedata['dns'] is True:
acme_mode = "--dns {0}".format(wo_acme_dns)
validation_mode = "DNS mode with {0}".format(wo_acme_dns)
else:
acme_mode = "-w /var/www/html"
validation_mode = "Webroot challenge"
Log.debug(self, "Validation : Webroot mode")
Log.info(self, "Validation mode : {0}".format(validation_mode))
Log.wait(self, "Issuing SSL cert with acme.sh")
try:
WOShellExec.cmd_exec(
self, "{0} ".format(wo_acme_exec) +
"--issue -d '{0}' {1} -k {2} -f"
.format(all_domains, acme_mode, keylenght))
except Exception as e:
Log.failed(self, "Issuing SSL cert with acme.sh")
Log.debug(self, str(e))
Log.error(self, "Unable to issue certificate", False)
if acmedata['dns'] is True:
Log.warn(
self, "Please make sure your properly "
"set your DNS API credentials for acme.sh")
else:
server_ip = requests.get('http://v4.wordops.eu/').text
for domain in wo_domain:
domain_ip = requests.get('http://v4.wordops.eu/dns/{0}/'
.format(domain)).text
if(not domain_ip == server_ip):
Log.warn(
self, "{0} is not pointing to your server IP"
.format(domain))
Log.error(
self, "You have to add the "
"proper DNS record", False)
break
else:
Log.error(
self, "Your domain is properly configured "
"but acme.sh was unable to issue certificate.\n"
"You can find more informations in "
"/var/log/wo/wordops.log", False)
return False
Log.valide(self, "Issuing SSL cert with acme.sh")
return True
def deploycert(self, wo_domain_name):
wo_acme_exec = ("/etc/letsencrypt/acme.sh --config-home "
"'/etc/letsencrypt/config'")
if not os.path.isfile('/etc/letsencrypt/{0}_ecc/fullchain.cer'
.format(wo_domain_name)):
Log.error(self, 'Certificate not found. Deployment canceled')
Log.debug(self, "Cert deployment for domain: {0}"
.format(wo_domain_name))
try:
Log.wait(self, "Deploying SSL cert")
if WOShellExec.cmd_exec(
self, "mkdir -p {0}/{1} && {2} --install-cert -d {1} --ecc "
"--cert-file {0}/{1}/cert.pem --key-file {0}/{1}/key.pem "
"--fullchain-file {0}/{1}/fullchain.pem "
"--ca-file {0}/{1}/ca.pem --reloadcmd \"nginx -t && "
"service nginx restart\" "
.format(WOVariables.wo_ssl_live,
wo_domain_name, wo_acme_exec)):
Log.valide(self, "Deploying SSL cert")
else:
Log.failed(self, "Deploying SSL cert")
Log.error(self, "Unable to deploy certificate")
if os.path.isdir('/var/www/{0}/conf/nginx'
.format(wo_domain_name)):
sslconf = open("/var/www/{0}/conf/nginx/ssl.conf"
.format(wo_domain_name),
encoding='utf-8', mode='w')
sslconf.write(
"listen 443 ssl http2;\n"
"listen [::]:443 ssl http2;\n"
"ssl_certificate {0}/{1}/fullchain.pem;\n"
"ssl_certificate_key {0}/{1}/key.pem;\n"
"ssl_trusted_certificate {0}/{1}/ca.pem;\n"
"ssl_stapling_verify on;\n"
.format(WOVariables.wo_ssl_live, wo_domain_name))
sslconf.close()
if not WOFileUtils.grep(self, '/var/www/22222/conf/nginx/ssl.conf',
'/etc/letsencrypt'):
Log.info(self, "Securing WordOps backend with current cert"
.format(wo_domain_name))
sslconf = open("/var/www/22222/conf/nginx/ssl.conf",
encoding='utf-8', mode='w')
sslconf.write("ssl_certificate {0}/{1}/fullchain.pem;\n"
"ssl_certificate_key {0}/{1}/key.pem;\n"
"ssl_trusted_certificate {0}/{1}/ca.pem;\n"
"ssl_stapling_verify on;\n"
.format(WOVariables.wo_ssl_live, wo_domain_name))
sslconf.close()
WOGit.add(self, ["/etc/letsencrypt"],
msg="Adding letsencrypt folder")
except IOError as e:
Log.debug(self, str(e))
Log.debug(self, "Error occured while generating "
"ssl.conf")