diff --git a/_modules/acme_sh.py b/_modules/acme_sh.py index f14570d..2c73297 100644 --- a/_modules/acme_sh.py +++ b/_modules/acme_sh.py @@ -9,161 +9,163 @@ import re import salt.utils.path -from salt.exceptions import CommandExecutionError, SaltInvocationError, CommandNotFoundError +from salt.exceptions import ( + CommandExecutionError, + SaltInvocationError, + CommandNotFoundError, +) log = logging.getLogger(__name__) -if '__context__' not in globals(): - __context__ = {} +if "__context__" not in globals(): + __context__ = {} -if '__opts__' not in globals(): - __opts__ = {} +if "__opts__" not in globals(): + __opts__ = {} -if '__salt__' not in globals(): - __salt__ = {} +if "__salt__" not in globals(): + __salt__ = {} def _get_acme_bin(home_dir): + script_path = f"{home_dir}/.acme.sh/acme.sh" - script_path = f"{home_dir}/.acme.sh/acme.sh" + if not salt.utils.path.which_bin([script_path]): + raise CommandNotFoundError( + f"{script_path} is not available, please install with `acme_sh.install my@example.com`" + ) - if not salt.utils.path.which_bin([script_path]): - raise CommandNotFoundError(f"{script_path} is not available, please install with `acme_sh.install my@example.com`") + return script_path - return script_path def _upgrade(home_dir, user): + acme_bin = _get_acme_bin(home_dir) + old_version = version(user) - acme_bin = _get_acme_bin(home_dir) - old_version = version(user) + cmd = [acme_bin, "--upgrade"] - cmd = [acme_bin, "--upgrade"] + upgrade = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) - upgrade = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) + if upgrade["retcode"] == 0: + new_version = version(user) + + if old_version == new_version: + ret = "Already up-to-date" + else: + ret = {"old": old_version, "new": new_version} - if upgrade["retcode"] == 0: - new_version = version(user) - - if old_version == new_version: - ret = "Already up-to-date" else: - ret = {"old": old_version, "new": new_version} + ret = upgrade - else: - ret = upgrade + return ret - return ret def _generate_crt_ret(name, cert_path): + return { + "certificate": f"{cert_path}/{name}/{name}.cer", + "private_key": f"{cert_path}/{name}/{name}.key", + "fullchain": f"{cert_path}/{name}/fullchain.cer", + "ca": f"{cert_path}/{name}/ca.cer", + } - return { - "certificate": f"{cert_path}/{name}/{name}.cer", - "private_key": f"{cert_path}/{name}/{name}.key", - "fullchain": f"{cert_path}/{name}/fullchain.cer", - "ca": f"{cert_path}/{name}/ca.cer" - } - -def install( - email, - user='root', - upgrade=False, - force=False -): - """ - Install acme.sh - email - used to register an account to acme server, you will receive a renewal notice email here +def install(email, user="root", upgrade=False, force=False): + """ + Install acme.sh + + email + used to register an account to acme server, you will receive a renewal notice email here - user - user to install - default: root + user + user to install + default: root - upgrade - upgrade acme.sh - default: False + upgrade + upgrade acme.sh + default: False - force - force installation - default: False - """ + force + force installation + default: False + """ - home_dir = __salt__["user.info"](user)["home"] - script_path = f"{home_dir}/.acme.sh/acme.sh" + home_dir = __salt__["user.info"](user)["home"] + script_path = f"{home_dir}/.acme.sh/acme.sh" - # if already installed - if __salt__["file.file_exists"](script_path): + # if already installed + if __salt__["file.file_exists"](script_path): + if upgrade: + return _upgrade(home_dir, user) - if upgrade: - return _upgrade(home_dir, user) + if not force: + return "Already installed, re-run with force=True to force installation" - if not force: - return "Already installed, re-run with force=True to force installation" + # clone https://github.com/acmesh-official/acme.sh + clone_name = f"acme.sh-{user}" + __salt__["git.clone"]( + "/tmp", url="https://github.com/acmesh-official/acme.sh", name=clone_name + ) - # clone https://github.com/acmesh-official/acme.sh - clone_name = f"acme.sh-{user}" - clone = __salt__["git.clone"]("/tmp", url="https://github.com/acmesh-official/acme.sh", name=clone_name) + if __context__["retcode"] == 1: + raise CommandExecutionError("Failed to clone the acme.sh repository") - if not clone: - CommandExecutionError("Failed to clone the acme.sh repository") + cmd = ["./acme.sh", "--install", "--accountemail", email, "--nocron"] - cmd = ["./acme.sh", "--install", "--accountemail", email, "--nocron"] + install_cmd = __salt__["cmd.run_all"]( + " ".join(cmd), cwd=f"/tmp/{clone_name}", runas=user + ) + __salt__["file.remove"](f"/tmp/{clone_name}") - install = __salt__["cmd.run_all"](" ".join(cmd), cwd=f"/tmp/{clone_name}", runas=user) - __salt__["file.remove"](f"/tmp/{clone_name}") - - # check install process successfully - if install["retcode"] == 0: - log.debug("acme.sh successfully installed") - else: - __context__["retcode"] = 1 - return "Installation failed, see logfiles for more information" - - return(f"acme.sh successfully installed in {os.path.dirname(script_path)}") + # check install process successfully + if install_cmd["retcode"] == 0: + log.debug("acme.sh successfully installed") + else: + __context__["retcode"] = 1 + return "Installation failed, see logfiles for more information" + return f"acme.sh successfully installed in {os.path.dirname(script_path)}" -def register( - email, - user='root' -): - - """ - Register account @ zeroSSL CA - This step is required if you are using zeroSSL CA - email - email to register +def register(email, user="root"): + """ + Register account @ zeroSSL CA + This step is required if you are using zeroSSL CA - user - run the command as a specified user - default: root - """ + email + email to register - home_dir = __salt__['user.info'](user)['home'] + user + run the command as a specified user + default: root + """ - acme_bin = _get_acme_bin(home_dir) + home_dir = __salt__["user.info"](user)["home"] - cmd = [acme_bin, "--register-account", "-m", email] + acme_bin = _get_acme_bin(home_dir) - register = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) + cmd = [acme_bin, "--register-account", "-m", email] - if register["retcode"] == 0: - match = re.search(r"ACCOUNT_THUMBPRINT='([^']+)'", register["stdout"]) - if match: - ret = {"account_thumbprint": match.group(1)} - else: - __context__["retcode"] = 1 - ret = "Account registration failed" + register_cmd = __salt__["cmd.run_all"]( + " ".join(cmd), python_shell=False, runas=user + ) - return ret + if register_cmd["retcode"] == 0: + match = re.search(r"ACCOUNT_THUMBPRINT='([^']+)'", register_cmd["stdout"]) + if match: + ret = {"account_thumbprint": match.group(1)} + else: + __context__["retcode"] = 1 + ret = "Account registration failed" + + return ret def issue( name, acme_mode, aliases=None, - server='letsencrypt', - keysize='4096', + server="letsencrypt", + keysize="4096", dns_plugin=None, webroot=None, http_port=None, @@ -171,343 +173,346 @@ def issue( cert_path=None, dns_credentials=None, force=False, - validTo=None, - validFrom=None + valid_to=None, + valid_from=None, ): - """ - Obtain a certificate - - name - Common name of the certificate - - acme_mode - choose one acme mode (webroot, standalone, standalone-tls-alpn, dns) - - aliases - comma seperated, subjectAltNames - - server - acme server - default = letsencrypt - - keysize - RSA key bits - default = 4096 - possible values: - ec-256 (prime256v1, "ECDSA P-256", which is the default key type) - ec-384 (secp384r1, "ECDSA P-384") - ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.) - 2048 (RSA2048) - 3072 (RSA3072) - 4096 (RSA4096) - - dns_plugin - dns plugin to use - see https://github.com/acmesh-official/acme.sh/wiki/dnsapi - - webroot - if acme_mode == webroot, full path to webroot - - http_port - the port the server listen on during standalone or standalone-tls-alpn - - user - run the command as a specified user - default: root - - cert_path - installation dir of certs - default = ~/.acme.sh - - dns_credentials - dns crededentials as python dict - see https://github.com/acmesh-official/acme.sh/wiki/dnsapi - - force - force issuing a certificate - default = False - - validTo - NotAfter field in cert - see https://github.com/acmesh-official/acme.sh/wiki/Validity - - validFrom - NotBefore field in cert - see https://github.com/acmesh-official/acme.sh/wiki/Validity - """ - - home_dir = __salt__['user.info'](user)['home'] - - if acme_mode == "standalone" or acme_mode == "standalone-tls-alpn": - # check socat is installed, when standalone - if not salt.utils.path.which_bin(["socat"]): - __context__["retcode"] = 1 - return "Install socat to use standalone mode first" - - acme_bin = _get_acme_bin(home_dir) - - # check keysize - possible_keylength = ["ec-256", "ec-384", "ec-521", "2048", "3072", "4096"] - if not keysize in possible_keylength: - raise SaltInvocationError(f"Keysize {keysize} not supported") - - # check mode - possible_mode = ["standalone", "standalone-tls-alpn", "dns", "webroot"] - if not acme_mode in possible_mode: - raise SaltInvocationError(f"Acme mode {acme_mode} not supported") - - # error when mode == webroot and webroot unset - if acme_mode == "webroot" and not webroot: - raise SaltInvocationError("Specify `webroot` path") - - # error when mode == dns and dns_plugin unset - if acme_mode == "dns" and not dns_plugin: - raise SaltInvocationError("Specify `dns_plugin`") - - # error when dns mode an no credentials specified - - if acme_mode =="dns" and not isinstance(dns_credentials, dict): - raise SaltInvocationError("Specify `dns_credentials` as dict") - - # build cmd - cmd = [acme_bin, "--issue", "-d", name, "--server", server, "--keylength", str(keysize)] - - # if aliases are specified - if aliases: - alias_cmd = [] - for domain in aliases.split(","): - alias_cmd.extend(["-d", domain]) - cmd.extend(alias_cmd) - - # if cert_path specified - if cert_path: - cmd.extend(["--cert-home", cert_path]) - else: - cert_path = f"{home_dir}/.acme.sh" - - # modes - if acme_mode == "webroot": - cmd.extend(["-w", webroot]) - elif acme_mode == "standalone": - cmd.append("--standalone") - if http_port: - cmd.extend(["--httpport", str(http_port)]) - elif acme_mode == "standalone-tls-alpn": - cmd.append("--alpn") - if http_port: - cmd.extend(["--tlsport", http_port]) - elif acme_mode == "dns": - cmd.extend(["--dns", dns_plugin]) - - # force - if force: - cmd.append("--force") - - # validity - if validTo: - cmd.extend["--valid-to", validTo] - if validFrom: - cmd.extend["--valid-from", validFrom] - - if acme_mode == "dns": - log.debug("Set dns_credentials as temporary env") - __salt__["environ.setenv"](dns_credentials) - - issue = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) - - if issue["retcode"] == 0: - ret = _generate_crt_ret(name, cert_path) - else: - if issue["stdout"].find("Next renewal time is") != -1: - ret = f"Certificate in {cert_path}/{name} is valid, re run with `force=True`" - elif issue["stdout"].find("acme.sh --register-account -m my@example.com") != -1: - ret = f"Please register your account: acme_sh.register my@example.com" + """ + Obtain a certificate + + name + Common name of the certificate + + acme_mode + choose one acme mode (webroot, standalone, standalone-tls-alpn, dns) + + aliases + comma seperated, subjectAltNames + + server + acme server + default = letsencrypt + + keysize + RSA key bits + default = 4096 + possible values: + ec-256 (prime256v1, "ECDSA P-256", which is the default key type) + ec-384 (secp384r1, "ECDSA P-384") + ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.) + 2048 (RSA2048) + 3072 (RSA3072) + 4096 (RSA4096) + + dns_plugin + dns plugin to use + see https://github.com/acmesh-official/acme.sh/wiki/dnsapi + + webroot + if acme_mode == webroot, full path to webroot + + http_port + the port the server listen on during standalone or standalone-tls-alpn + + user + run the command as a specified user + default: root + + cert_path + installation dir of certs + default = ~/.acme.sh + + dns_credentials + dns crededentials as python dict + see https://github.com/acmesh-official/acme.sh/wiki/dnsapi + + force + force issuing a certificate + default = False + + valid_to + NotAfter field in cert + see https://github.com/acmesh-official/acme.sh/wiki/Validity + + valid_from + NotBefore field in cert + see https://github.com/acmesh-official/acme.sh/wiki/Validity + """ + + home_dir = __salt__["user.info"](user)["home"] + + if acme_mode == "standalone" or acme_mode == "standalone-tls-alpn": + # check socat is installed, when standalone + if not salt.utils.path.which_bin(["socat"]): + __context__["retcode"] = 1 + return "Install socat to use standalone mode first" + + acme_bin = _get_acme_bin(home_dir) + + # check keysize + possible_keylength = ["ec-256", "ec-384", "ec-521", "2048", "3072", "4096"] + if keysize not in possible_keylength: + raise SaltInvocationError(f"Keysize {keysize} not supported") + + # check mode + possible_mode = ["standalone", "standalone-tls-alpn", "dns", "webroot"] + if not acme_mode in possible_mode: + raise SaltInvocationError(f"Acme mode {acme_mode} not supported") + + # error when mode == webroot and webroot unset + if acme_mode == "webroot" and not webroot: + raise SaltInvocationError("Specify `webroot` path") + + # error when mode == dns and dns_plugin unset + if acme_mode == "dns" and not dns_plugin: + raise SaltInvocationError("Specify `dns_plugin`") + + # error when dns mode an no credentials specified + + if acme_mode == "dns" and not isinstance(dns_credentials, dict): + raise SaltInvocationError("Specify `dns_credentials` as dict") + + # build cmd + cmd = [ + acme_bin, + "--issue", + "-d", + name, + "--server", + server, + "--keylength", + str(keysize), + ] + + # if aliases are specified + if aliases: + alias_cmd = [] + for domain in aliases.split(","): + alias_cmd.extend(["-d", domain]) + cmd.extend(alias_cmd) + + # if cert_path specified + if cert_path: + cmd.extend(["--cert-home", cert_path]) else: - ret = issue - - return ret - -def list( - user='root', - cert_path=None -): + cert_path = f"{home_dir}/.acme.sh" + + # modes + if acme_mode == "webroot": + cmd.extend(["-w", webroot]) + elif acme_mode == "standalone": + cmd.append("--standalone") + if http_port: + cmd.extend(["--httpport", str(http_port)]) + elif acme_mode == "standalone-tls-alpn": + cmd.append("--alpn") + if http_port: + cmd.extend(["--tlsport", http_port]) + elif acme_mode == "dns": + cmd.extend(["--dns", dns_plugin]) + + # force + if force: + cmd.append("--force") + + # validity + if valid_to: + cmd.extend(["--valid-to", valid_to]) + if valid_from: + cmd.extend(["--valid-from", valid_from]) + + if acme_mode == "dns": + log.debug("Set dns_credentials as temporary env") + __salt__["environ.setenv"](dns_credentials) + + issue_cmd = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) + + if issue_cmd["retcode"] == 0: + ret = _generate_crt_ret(name, cert_path) + else: + if issue_cmd["stdout"].find("Next renewal time is") != -1: + ret = ( + f"Certificate in {cert_path}/{name} is valid, re run with `force=True`" + ) + elif ( + issue_cmd["stdout"].find("acme.sh --register-account -m my@example.com") + != -1 + ): + ret = "Please register your account: acme_sh.register my@example.com" + else: + ret = issue_cmd + + return ret + + +def list_crt(user="root", cert_path=None): + """ + List all certificates in given cert_path + + user + run the command as a specified user + default: root + + cert_path + installation dir of certs + default = ~/.acme.sh + """ + + home_dir = __salt__["user.info"](user)["home"] + + acme_bin = _get_acme_bin(home_dir) + + cmd = [acme_bin, "--list"] + + if cert_path: + cmd.extend(["--cert-home", cert_path]) + + list_crt_cmd = __salt__["cmd.run_all"]( + " ".join(cmd), python_shell=False, runas=user + ) + + if list_crt_cmd["retcode"] == 0: + # map output + lines = list_crt_cmd["stdout"].strip().split("\n") + keys = lines[0].split() + ret = [] + for line in lines[1:]: + values = line.split() + entry = dict(zip(keys, values)) + ret.append(entry) + else: + ret = list_crt_cmd - """ - List all certificates in given cert_path + return ret - user - run the command as a specified user - default: root - cert_path - installation dir of certs - default = ~/.acme.sh - """ +def info(name, user="root", cert_path=None): + """ + Get info about a certificate - home_dir = __salt__["user.info"](user)["home"] + name + Common name of the certificate (main_domain) - acme_bin = _get_acme_bin(home_dir) + user + run the command as a specified user + default: root - cmd = [acme_bin, "--list"] + cert_path + installation dir of certs + default: ~/.acme.sh + """ - if cert_path: - cmd.extend["--cert-home", cert_path] + if "acme_sh.info" not in __context__: + __context__["acme_sh.info"] = {"code": 0} - list_crt = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) + home_dir = __salt__["user.info"](user)["home"] - if list_crt["retcode"] == 0: - # map output - lines = list_crt["stdout"].strip().split('\n') - keys = lines[0].split() - ret = [] - for line in lines[1:]: - values = line.split() - entry = dict(zip(keys, values)) - ret.append(entry) - else: - ret = list_crt + acme_bin = _get_acme_bin(home_dir) - return ret + cmd = [acme_bin, "--info", "--domain", name] -def info( - name, - user='root', - cert_path=None -): - """ - Get info about a certificate + if cert_path: + cmd.extend(["--cert-home", cert_path]) + else: + cert_path = f"{home_dir}/.acme.sh" + + # check if cert_path exists + if not __salt__["file.directory_exists"](cert_path): + __context__["acme_sh.info"]["code"] = 1 + __context__["retcode"] = 1 + return f"Certificate path {cert_path} does not exist" + # check if cert exists + if not __salt__["file.directory_exists"](f"{cert_path}/{name}"): + __context__["acme_sh.info"]["code"] = 1 + __context__["retcode"] = 1 + return f"Certificate {name} does not exist" + + info_cmd = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) + + if info_cmd["retcode"] == 0: + # map output to dict + info_dict = {} + lines = info_cmd["stdout"].strip().split("\n") + for line in lines: + key, value = line.split("=") + info_dict[key] = value + + ret = info_dict + else: + ret = info_cmd - name - Common name of the certificate (main_domain) + return ret - user - run the command as a specified user - default: root - cert_path - installation dir of certs - default: ~/.acme.sh - """ +def renew(name, user="root", cert_path=None, force=False): + """ + Renew a certificate - if not "acme_sh.info" in __context__: - __context__["acme_sh.info"] = {"code": 0} + name + Common name of the certificate (main_domain) - home_dir = __salt__["user.info"](user)["home"] + user + run the command as a specified user + default: root - acme_bin = _get_acme_bin(home_dir) + cert_path + installation dir of certs + default: ~/.acme.sh - cmd = [acme_bin, "--info", "--domain", name] + force + force renewing a certificate + default: False + """ - if cert_path: - cmd.extend(["--cert-home", cert_path]) - else: - cert_path = f"{home_dir}/.acme.sh" + home_dir = __salt__["user.info"](user)["home"] - # check if cert_path exists - if not __salt__["file.directory_exists"](cert_path): - __context__["acme_sh.info"]["code"] = 1 - __context__["retcode"] = 1 - return f"Certificate path {cert_path} does not exist" - # check if cert exists - if not __salt__["file.directory_exists"](f"{cert_path}/{name}"): - __context__["acme_sh.info"]["code"] = 1 - __context__["retcode"] = 1 - return f"Certificate {name} does not exist" + acme_bin = _get_acme_bin(home_dir) - info = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) + cmd = [acme_bin, "--renew", "--domain", name] - if info["retcode"] == 0: - # map output to dict - info_dict = {} - lines = info["stdout"].strip().split('\n') - for line in lines: - key, value = line.split('=') - info_dict[key] = value + if cert_path: + cmd.extend(["--cert-home", cert_path]) + else: + cert_path = f"{home_dir}/.acme.sh" - ret = info_dict - else: - ret = info + if force: + cmd.append("--force") - return ret + renew_cmd = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) -def renew( - name, - user='root', - cert_path=None, - force=False -): - - """ - Renew a certificate - - name - Common name of the certificate (main_domain) - - user - run the command as a specified user - default: root - - cert_path - installation dir of certs - default: ~/.acme.sh - - force - force renewing a certificate - default: False - """ - - home_dir = __salt__["user.info"](user)["home"] - - acme_bin = _get_acme_bin(home_dir) - - cmd = [acme_bin, "--renew", "--domain", name] - - if cert_path: - cmd.extend(["--cert-home", cert_path]) - else: - cert_path = f"{home_dir}/.acme.sh" - - if force: - cmd.append("--force") - - renew = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) - - if renew["retcode"] == 0: - ret = _generate_crt_ret(name, cert_path) - else: - next_renew = re.search(r"Next renewal time is: (.*)", renew["stdout"]) - if next_renew: - ret = f"Next renewal time is {next_renew.group(1)}, add force=True to renew" - elif renew["stdout"].find("is not an issued domain, skip") != -1: - ret = f"Domain {name} is not an issued domain" + if renew_cmd["retcode"] == 0: + ret = _generate_crt_ret(name, cert_path) else: - ret = renew + next_renew = re.search(r"Next renewal time is: (.*)", renew_cmd["stdout"]) + if next_renew: + ret = f"Next renewal time is {next_renew.group(1)}, add force=True to renew" + elif renew_cmd["stdout"].find("is not an issued domain, skip") != -1: + ret = f"Domain {name} is not an issued domain" + else: + ret = renew_cmd - return ret + return ret -def version( - user='root' -): - """ - Get version of acme.sh +def version(user="root"): + """ + Get version of acme.sh - user - run the command as a specified user - default: root - """ + user + run the command as a specified user + default: root + """ - home_dir = __salt__["user.info"](user)["home"] + home_dir = __salt__["user.info"](user)["home"] - acme_bin = _get_acme_bin(home_dir) + acme_bin = _get_acme_bin(home_dir) - cmd = [acme_bin, "--version"] + cmd = [acme_bin, "--version"] - version_cmd = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) + version_cmd = __salt__["cmd.run_all"](" ".join(cmd), python_shell=False, runas=user) - if version_cmd["retcode"] == 0: - ret = re.search(r"v(.*)", version_cmd["stdout"]).group(1) - else: - ret = version_cmd + if version_cmd["retcode"] == 0: + ret = re.search(r"v(.*)", version_cmd["stdout"]).group(1) + else: + ret = version_cmd - return ret + return ret diff --git a/_states/acme_sh.py b/_states/acme_sh.py index 4c06396..0645de7 100644 --- a/_states/acme_sh.py +++ b/_states/acme_sh.py @@ -5,285 +5,300 @@ """ import time -import salt.exceptions import logging +import salt.exceptions log = logging.getLogger(__name__) -if '__context__' not in globals(): - __context__ = {} +if "__context__" not in globals(): + __context__ = {} + +if "__opts__" not in globals(): + __opts__ = {} -if '__opts__' not in globals(): - __opts__ = {} +if "__salt__" not in globals(): + __salt__ = {} -if '__salt__' not in globals(): - __salt__ = {} def __virtual__(): - """ - Only load if the acme_sh module is available in __salt__ - """ - if "acme_sh.issue" in __salt__: - return "acme_sh" - return False, "acme_sh module could not be loaded" + """ + Only load if the acme_sh module is available in __salt__ + """ + if "acme_sh.issue" in __salt__: + return "acme_sh" + return False, "acme_sh module could not be loaded" + def installed( - name, - email, - user="root", - upgrade=False, - force=False, - ): - """ - Ensure that acme.sh is installed - - email - Email address to use for acme registration - - user - User to install acme.sh for - - upgrade - Upgrade acme.sh if already installed - - force - Force reinstallation of acme.sh - """ - - ret = { - "name": name, - "changes": {}, - "result": True, - "comment": "", - } - - home_dir = __salt__["user.info"](user)["home"] - - # if acme.sh is not installed or force is enabled - if not __salt__["file.file_exists"](home_dir + "/.acme.sh/acme.sh"): - # if test mode is enabled - if __opts__["test"]: - ret["result"] = None - ret["comment"] = "acme.sh would be installed" - return ret - - # Install acme.sh - if __salt__["acme_sh.install"](email, user=user): - ret["changes"]["acme_sh"] = "Installed" - ret["comment"] = "acme.sh has been installed" - # if failed to install acme.sh - else: - ret["result"] = False - ret["comment"] = "Failed to install acme.sh" - return ret - # if acme.sh is installed and force is enabled - elif force: - # if test mode is enabled - if __opts__["test"]: - ret["result"] = None - ret["comment"] = "acme.sh would be reinstalled" - return ret - - # Reinstall acme.sh - if __salt__["acme_sh.install"](email, user=user, force=force): - ret["changes"]["acme_sh"] = "Reinstalled" - ret["comment"] = "acme.sh has been reinstalled" - # if failed to reinstall acme.sh + name, + email, + user="root", + upgrade=False, + force=False, +): + """ + Ensure that acme.sh is installed + + email + Email address to use for acme registration + + user + User to install acme.sh for + + upgrade + Upgrade acme.sh if already installed + + force + Force reinstallation of acme.sh + """ + + ret = { + "name": name, + "changes": {}, + "result": True, + "comment": "", + } + + home_dir = __salt__["user.info"](user)["home"] + + # if acme.sh is not installed or force is enabled + if not __salt__["file.file_exists"](home_dir + "/.acme.sh/acme.sh"): + # if test mode is enabled + if __opts__["test"]: + ret["result"] = None + ret["comment"] = "acme.sh would be installed" + return ret + + # Install acme.sh + if __salt__["acme_sh.install"](email, user=user): + ret["changes"]["acme_sh"] = "Installed" + ret["comment"] = "acme.sh has been installed" + # if failed to install acme.sh + else: + ret["result"] = False + ret["comment"] = "Failed to install acme.sh" + return ret + # if acme.sh is installed and force is enabled + elif force: + # if test mode is enabled + if __opts__["test"]: + ret["result"] = None + ret["comment"] = "acme.sh would be reinstalled" + return ret + + # Reinstall acme.sh + if __salt__["acme_sh.install"](email, user=user, force=force): + ret["changes"]["acme_sh"] = "Reinstalled" + ret["comment"] = "acme.sh has been reinstalled" + # if failed to reinstall acme.sh + else: + ret["result"] = False + ret["comment"] = "Failed to reinstall acme.sh" + return ret + # if upgrade is enabled + elif upgrade: + # if test mode is enabled + if __opts__["test"]: + ret["result"] = None + ret["comment"] = "acme.sh would be upgraded" + return ret + + upgrade_cmd = __salt__["acme_sh.install"](email, user=user, upgrade=upgrade) + if isinstance(upgrade_cmd, str): + ret["comment"] = "Up-to-date" + elif isinstance(upgrade_cmd, dict): + ret["changes"]["acme_sh"] = upgrade_cmd + ret["comment"] = "acme.sh has been upgraded" else: - ret["result"] = False - ret["comment"] = "Failed to reinstall acme.sh" + # if acme.sh is already installed + ret["comment"] = "acme.sh is already installed" + return ret - # if upgrade is enabled - elif upgrade: - # if test mode is enabled - if __opts__["test"]: - ret["result"] = None - ret["comment"] = "acme.sh would be upgraded" - return ret - - upgrade_cmd = __salt__["acme_sh.install"](email, user=user, upgrade=upgrade) - if isinstance(upgrade_cmd, str): - ret["comment"] = "Up-to-date" - elif isinstance(upgrade_cmd, dict): - ret["changes"]["acme_sh"] = upgrade_cmd - ret["comment"] = "acme.sh has been upgraded" - else: - # if acme.sh is already installed - ret["comment"] = "acme.sh is already installed" - - return ret + def cert( - name, - acme_mode, - aliases=[], - server=None, - keysize="4096", - dns_plugin=None, - webroot=None, - http_port=None, - user="root", - cert_path=None, - dns_credentials=None, - force=False, - validTo=None, - validFrom=None, - ): - """ - Ensure that a certificate is issued - - name - Domain name to issue certificate for - - acme_mode - ACME mode to use for certificate issuance (webroot, standalone, standalone-tls-alpn, dns) - - aliases - List of aliases to issue certificate for - - server - ACME server to use for certificate issuance - - keysize - Key size to use for certificate issuance - default = 4096 - possible values: - ec-256 (prime256v1, "ECDSA P-256", which is the default key type) - ec-384 (secp384r1, "ECDSA P-384") - ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.) - 2048 (RSA2048) - 3072 (RSA3072) - 4096 (RSA4096) - - dns_plugin - DNS plugin to use for certificate issuance - see https://github.com/acmesh-official/acme.sh/wiki/dnsapi - - webroot - Webroot to use for certificate issuance - Full path needed, user needs write access to this directory - - http_port - Port to use for certificate issuance - default = 80 - - user - User to issue certificate for - - cert_path - Path to store certificate at - default = ~/.acme.sh - - dns_credentials - Dictionary of credentials to use for DNS plugin - Every value needs to be a string - see https://github.com/acmesh-official/acme.sh/wiki/dnsapi - - force - Force reissue of certificate - - validTo - NotAfter field in cert - see https://github.com/acmesh-official/acme.sh/wiki/Validity - - validFrom - NotBefore field in cert - see https://github.com/acmesh-official/acme.sh/wiki/Validity - """ - - ret = { - "name": name, - "changes": {}, - "result": True, - "comment": "", - } - - # error checking - - if aliases and not isinstance(aliases, list): - raise salt.exceptions.SaltInvocationError("aliases must be a list") - - if dns_credentials and not isinstance(dns_credentials, dict): - raise salt.exceptions.SaltInvocationError("dns_credentials must be a dictionary") - - if validTo and not isinstance(validTo, str): - raise salt.exceptions.SaltInvocationError("validTo must be a string") - - if validFrom and not isinstance(validFrom, str): - raise salt.exceptions.SaltInvocationError("validFrom must be a string") - - if acme_mode == "dns" and not dns_plugin: - raise salt.exceptions.SaltInvocationError("dns_plugin must be specified when acme_mode is dns") - - if acme_mode == "dns" and not dns_credentials: - raise salt.exceptions.SaltInvocationError("dns_credentials must be specified when acme_mode is dns") - - if acme_mode == "webroot" and not webroot: - raise salt.exceptions.SaltInvocationError("webroot must be specified when acme_mode is webroot") - - # check cert is available and set for renewal - - crt_info = __salt__["acme_sh.info"](name, user=user, cert_path=cert_path) - - if __context__["acme_sh.info"]["code"] == 1 or "Le_NextRenewTime" not in crt_info or force: - log.debug("Certificate is not available or force is enabled") - # if test mode is enabled - if __opts__["test"]: - ret["result"] = None - ret["comment"] = "Certificate would be issued" - return ret - - # issue certificate - issue = __salt__["acme_sh.issue"]( - name, - acme_mode, - aliases=",".join(aliases), - server=server, - keysize=keysize, - dns_plugin=dns_plugin, - webroot=webroot, - http_port=http_port, - user=user, - cert_path=cert_path, - dns_credentials=dns_credentials, - force=force, - validTo=validTo, - validFrom=validFrom, - ) - - if __context__["retcode"] == 0: - ret["changes"][name] = issue - ret["comment"] = "Certificate has been issued" - # if failed to issue certificate + name, + acme_mode, + aliases=None, + server=None, + keysize="4096", + dns_plugin=None, + webroot=None, + http_port=None, + user="root", + cert_path=None, + dns_credentials=None, + force=False, + valid_to=None, + valid_from=None, +): + """ + Ensure that a certificate is issued + + name + Domain name to issue certificate for + + acme_mode + ACME mode to use for certificate issuance (webroot, standalone, standalone-tls-alpn, dns) + + aliases + List of aliases to issue certificate for + + server + ACME server to use for certificate issuance + + keysize + Key size to use for certificate issuance + default = 4096 + possible values: + ec-256 (prime256v1, "ECDSA P-256", which is the default key type) + ec-384 (secp384r1, "ECDSA P-384") + ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.) + 2048 (RSA2048) + 3072 (RSA3072) + 4096 (RSA4096) + + dns_plugin + DNS plugin to use for certificate issuance + see https://github.com/acmesh-official/acme.sh/wiki/dnsapi + + webroot + Webroot to use for certificate issuance + Full path needed, user needs write access to this directory + + http_port + Port to use for certificate issuance + default = 80 + + user + User to issue certificate for + + cert_path + Path to store certificate at + default = ~/.acme.sh + + dns_credentials + Dictionary of credentials to use for DNS plugin + Every value needs to be a string + see https://github.com/acmesh-official/acme.sh/wiki/dnsapi + + force + Force reissue of certificate + + valid_to + NotAfter field in cert + see https://github.com/acmesh-official/acme.sh/wiki/Validity + + valid_from + NotBefore field in cert + see https://github.com/acmesh-official/acme.sh/wiki/Validity + """ + + ret = { + "name": name, + "changes": {}, + "result": True, + "comment": "", + } + + # error checking + + if aliases and not isinstance(aliases, list): + raise salt.exceptions.SaltInvocationError("aliases must be a list") + + if dns_credentials and not isinstance(dns_credentials, dict): + raise salt.exceptions.SaltInvocationError( + "dns_credentials must be a dictionary" + ) + + if valid_to and not isinstance(valid_to, str): + raise salt.exceptions.SaltInvocationError("valid_to must be a string") + + if valid_from and not isinstance(valid_from, str): + raise salt.exceptions.SaltInvocationError("valid_from must be a string") + + if acme_mode == "dns" and not dns_plugin: + raise salt.exceptions.SaltInvocationError( + "dns_plugin must be specified when acme_mode is dns" + ) + + if acme_mode == "dns" and not dns_credentials: + raise salt.exceptions.SaltInvocationError( + "dns_credentials must be specified when acme_mode is dns" + ) + + if acme_mode == "webroot" and not webroot: + raise salt.exceptions.SaltInvocationError( + "webroot must be specified when acme_mode is webroot" + ) + + # check cert is available and set for renewal + + crt_info = __salt__["acme_sh.info"](name, user=user, cert_path=cert_path) + + if ( + __context__["acme_sh.info"]["code"] == 1 + or "Le_NextRenewTime" not in crt_info + or force + ): + log.debug("Certificate is not available or force is enabled") + # if test mode is enabled + if __opts__["test"]: + ret["result"] = None + ret["comment"] = "Certificate would be issued" + return ret + + # issue certificate + issue = __salt__["acme_sh.issue"]( + name, + acme_mode, + aliases=",".join(aliases) if aliases else None, + server=server, + keysize=keysize, + dns_plugin=dns_plugin, + webroot=webroot, + http_port=http_port, + user=user, + cert_path=cert_path, + dns_credentials=dns_credentials, + force=force, + valid_to=valid_to, + valid_from=valid_from, + ) + + if __context__["retcode"] == 0: + ret["changes"][name] = issue + ret["comment"] = "Certificate has been issued" + # if failed to issue certificate + else: + ret["result"] = False + ret["comment"] = issue["stderr"] + + # if certificate is available and set for renewal + elif int(time.time()) > int(crt_info["Le_NextRenewTime"]): + log.debug("Certificate is available and set for renewal") + # if test mode is enabled + if __opts__["test"]: + ret["result"] = None + ret["comment"] = "Certificate would be renewed" + return ret + + # renew certificate + renew = __salt__["acme_sh.renew"]( + name, + user=user, + cert_path=cert_path, + force=force, + ) + + if __context__["retcode"] == 0: + ret["changes"][name] = renew + ret["comment"] = "Certificate has been renewed" + # if failed to renew certificate + else: + ret["result"] = False + ret["comment"] = renew["stderr"] else: - ret["result"] = False - ret["comment"] = issue["stderr"] - - # if certificate is available and set for renewal - elif int(time.time()) > int(crt_info["Le_NextRenewTime"]): - log.debug("Certificate is available and set for renewal") - # if test mode is enabled - if __opts__["test"]: - ret["result"] = None - ret["comment"] = "Certificate would be renewed" - return ret - - # renew certificate - renew = __salt__["acme_sh.renew"]( - name, - user=user, - cert_path=cert_path, - force=force, - ) - - if __context__["retcode"] == 0: - ret["changes"][name] = renew - ret["comment"] = "Certificate has been renewed" - # if failed to renew certificate - else: - ret["result"] = False - ret["comment"] = renew["stderr"] - else: - ret["comment"] = "Certificate is already up-to-date" + ret["comment"] = "Certificate is already up-to-date" - return ret + return ret