Compare commits
5 commits
main
...
dev-tls_au
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d18600bf91 | ||
|
|
9806adb9ab | ||
|
|
7b3d5829ae | ||
|
|
0aa3cb5303 | ||
|
|
f25589f56b |
|
|
@ -3,7 +3,6 @@
|
||||||
"var_tlscert_acme_inwx_acme_account_key_path": "/etc/letsencrypt/key",
|
"var_tlscert_acme_inwx_acme_account_key_path": "/etc/letsencrypt/key",
|
||||||
"var_tlscert_acme_inwx_inwx_account_username": "REPLACE_ME",
|
"var_tlscert_acme_inwx_inwx_account_username": "REPLACE_ME",
|
||||||
"var_tlscert_acme_inwx_inwx_account_password": "REPLACE_ME",
|
"var_tlscert_acme_inwx_inwx_account_password": "REPLACE_ME",
|
||||||
"var_tlscert_acme_inwx_domain_base": "example.org",
|
"var_tlscert_acme_inwx_domain": "foo.example.org",
|
||||||
"var_tlscert_acme_inwx_domain_path": "foo",
|
|
||||||
"var_tlscert_acme_inwx_ssl_directory": "/etc/ssl"
|
"var_tlscert_acme_inwx_ssl_directory": "/etc/ssl"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,32 @@ import argparse as _argparse
|
||||||
import pathlib as _pathlib
|
import pathlib as _pathlib
|
||||||
import time as _time
|
import time as _time
|
||||||
|
|
||||||
|
def convey(x, fs):
|
||||||
|
y = x
|
||||||
|
for f in fs:
|
||||||
|
y = f(y)
|
||||||
|
return y
|
||||||
|
|
||||||
|
|
||||||
|
def string_coin(
|
||||||
|
template : str,
|
||||||
|
arguments : dict
|
||||||
|
):
|
||||||
|
result = template
|
||||||
|
for (key, value, ) in arguments.items():
|
||||||
|
result = result.replace("{{%s}}" % key, value)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def file_read(
|
||||||
|
path : str
|
||||||
|
):
|
||||||
|
handle = open(path, "r")
|
||||||
|
content = handle.read()
|
||||||
|
handle.close()
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
def log(
|
def log(
|
||||||
messsage : str
|
messsage : str
|
||||||
):
|
):
|
||||||
|
|
@ -28,30 +54,6 @@ def path_read(
|
||||||
return position
|
return position
|
||||||
|
|
||||||
|
|
||||||
def path_write(
|
|
||||||
thing,
|
|
||||||
steps : List[str],
|
|
||||||
value
|
|
||||||
):
|
|
||||||
steps_first = steps[:-1]
|
|
||||||
step_last = steps[-1]
|
|
||||||
position = thing
|
|
||||||
for step in steps_first:
|
|
||||||
if (not (step in position)):
|
|
||||||
position[step] = {}
|
|
||||||
position = position[step]
|
|
||||||
position[step_last] = value
|
|
||||||
|
|
||||||
|
|
||||||
def merge(
|
|
||||||
core,
|
|
||||||
mantle
|
|
||||||
):
|
|
||||||
result = core.copy()
|
|
||||||
result.update(mantle)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def http_call(
|
def http_call(
|
||||||
request : dict,
|
request : dict,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
@ -76,8 +78,83 @@ def http_call(
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
_conf_data = {
|
_conf_data = None
|
||||||
|
|
||||||
|
|
||||||
|
def conf_schema(
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
"url": {
|
"url": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
},
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"scheme": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"port": {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"required": [
|
||||||
|
"host",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"username": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"required": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": False,
|
||||||
|
"required": [
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def conf_load(
|
||||||
|
path : str
|
||||||
|
):
|
||||||
|
global _conf_data
|
||||||
|
if (not _os.path.exists(path)):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
conf_data_raw = _json.loads(file_read(path))
|
||||||
|
for pair in conf_data_raw.get("url", {}).items():
|
||||||
|
if ("host" in pair[1]):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError("flawed conf: missing mandatory value 'host' for url entry '%s'" % pair[0])
|
||||||
|
_conf_data = {
|
||||||
|
"url": convey(
|
||||||
|
(
|
||||||
|
{
|
||||||
"test": {
|
"test": {
|
||||||
"scheme": "https",
|
"scheme": "https",
|
||||||
"host": "api.ote.domrobot.com",
|
"host": "api.ote.domrobot.com",
|
||||||
|
|
@ -90,27 +167,34 @@ _conf_data = {
|
||||||
"port": 443,
|
"port": 443,
|
||||||
"path": "jsonrpc/"
|
"path": "jsonrpc/"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"environment": "production",
|
|
||||||
"account": {
|
|
||||||
"username": None,
|
|
||||||
"password": None
|
|
||||||
}
|
}
|
||||||
}
|
|
|
||||||
|
conf_data_raw.get("url", {})
|
||||||
|
),
|
||||||
def conf_load(
|
[
|
||||||
path : str
|
lambda x: x.items(),
|
||||||
):
|
lambda pairs: map(
|
||||||
global _conf_data
|
lambda pair: (
|
||||||
if (not _os.path.exists(path)):
|
pair[0],
|
||||||
pass
|
{
|
||||||
else:
|
"scheme": pair[1].get("scheme", "https"),
|
||||||
handle = open(path, "r")
|
"host": pair[1]["host"],
|
||||||
content = handle.read()
|
"port": pair[1].get("port", 443),
|
||||||
handle.close()
|
"path": pair[1].get("path", "jsonrpc/"),
|
||||||
data = _json.loads(content)
|
}
|
||||||
_conf_data = merge(_conf_data, data)
|
),
|
||||||
|
pairs
|
||||||
|
),
|
||||||
|
dict,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
"environment": conf_data_raw.get("environment", "production"),
|
||||||
|
"account": {
|
||||||
|
"username": conf_data_raw.get("account", {}).get("username", None),
|
||||||
|
"password": conf_data_raw.get("account", {}).get("password", None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print(_json.dumps(_conf_data, indent = "\t"))
|
||||||
|
|
||||||
|
|
||||||
def conf_get(
|
def conf_get(
|
||||||
|
|
@ -119,15 +203,6 @@ def conf_get(
|
||||||
global _conf_data
|
global _conf_data
|
||||||
return path_read(_conf_data, path.split("."))
|
return path_read(_conf_data, path.split("."))
|
||||||
|
|
||||||
|
|
||||||
def conf_set(
|
|
||||||
path : str,
|
|
||||||
value
|
|
||||||
):
|
|
||||||
global _conf_data
|
|
||||||
path_write(_conf_data, path.split("."), value)
|
|
||||||
|
|
||||||
|
|
||||||
def api_call(
|
def api_call(
|
||||||
environment : str,
|
environment : str,
|
||||||
accesstoken : str,
|
accesstoken : str,
|
||||||
|
|
@ -172,6 +247,9 @@ def api_call(
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.login
|
||||||
|
'''
|
||||||
def api_macro_login(
|
def api_macro_login(
|
||||||
environment : str,
|
environment : str,
|
||||||
username : str,
|
username : str,
|
||||||
|
|
@ -195,6 +273,9 @@ def api_macro_login(
|
||||||
return response["_accesstoken"]
|
return response["_accesstoken"]
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.logout
|
||||||
|
'''
|
||||||
def api_macro_logout(
|
def api_macro_logout(
|
||||||
environment : str,
|
environment : str,
|
||||||
accesstoken : str
|
accesstoken : str
|
||||||
|
|
@ -210,6 +291,9 @@ def api_macro_logout(
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02.html#account.info
|
||||||
|
'''
|
||||||
def api_macro_info(
|
def api_macro_info(
|
||||||
environment : str,
|
environment : str,
|
||||||
username : str,
|
username : str,
|
||||||
|
|
@ -228,6 +312,9 @@ def api_macro_info(
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.info
|
||||||
|
'''
|
||||||
def api_macro_list(
|
def api_macro_list(
|
||||||
environment : str,
|
environment : str,
|
||||||
username : str,
|
username : str,
|
||||||
|
|
@ -248,12 +335,17 @@ def api_macro_list(
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.info
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.createRecord
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.updateRecord
|
||||||
|
'''
|
||||||
def api_macro_save(
|
def api_macro_save(
|
||||||
environment : str,
|
environment : str,
|
||||||
username : str,
|
username : str,
|
||||||
password : str,
|
password : str,
|
||||||
domain : str,
|
domain_base : str,
|
||||||
name : str,
|
domain_path,
|
||||||
type_ : str,
|
type_ : str,
|
||||||
content : str
|
content : str
|
||||||
):
|
):
|
||||||
|
|
@ -264,12 +356,28 @@ def api_macro_save(
|
||||||
"nameserver",
|
"nameserver",
|
||||||
"info",
|
"info",
|
||||||
{
|
{
|
||||||
"domain": domain,
|
"domain": domain_base,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
matching = list(
|
matching = list(
|
||||||
filter(
|
filter(
|
||||||
lambda record: ((record["name"] == (name + "." + domain)) and (record["type"] == type_)),
|
lambda record: (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(domain_path is None)
|
||||||
|
and
|
||||||
|
(record["name"] == domain)
|
||||||
|
)
|
||||||
|
or
|
||||||
|
(
|
||||||
|
(domain_path is not None)
|
||||||
|
and
|
||||||
|
(record["name"] == (domain_path + "." + domain_base))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and
|
||||||
|
(record["type"] == type_)
|
||||||
|
),
|
||||||
info["record"]
|
info["record"]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -281,8 +389,8 @@ def api_macro_save(
|
||||||
"nameserver",
|
"nameserver",
|
||||||
"createRecord",
|
"createRecord",
|
||||||
{
|
{
|
||||||
"domain": domain,
|
"domain": domain_base,
|
||||||
"name": name,
|
"name": domain_path,
|
||||||
"type": type_,
|
"type": type_,
|
||||||
"content": content,
|
"content": content,
|
||||||
}
|
}
|
||||||
|
|
@ -308,153 +416,324 @@ def api_macro_save(
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def args(
|
'''
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.info
|
||||||
|
@see https://www.inwx.de/de/help/apidoc/f/ch02s15.html#nameserver.deleteRecord
|
||||||
|
'''
|
||||||
|
def api_macro_delete(
|
||||||
|
environment : str,
|
||||||
|
username : str,
|
||||||
|
password : str,
|
||||||
|
domain_base : str,
|
||||||
|
domain_path,
|
||||||
|
type_
|
||||||
):
|
):
|
||||||
argumentparser = _argparse.ArgumentParser(
|
accesstoken = api_macro_login(environment, username, password)
|
||||||
|
info = api_call(
|
||||||
|
environment,
|
||||||
|
accesstoken,
|
||||||
|
"nameserver",
|
||||||
|
"info",
|
||||||
|
{
|
||||||
|
"domain": domain_base,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
matching = list(
|
||||||
|
filter(
|
||||||
|
lambda record: (
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(domain_path is None)
|
||||||
|
and
|
||||||
|
(record["name"] == domain_base)
|
||||||
|
)
|
||||||
|
or
|
||||||
|
(
|
||||||
|
(domain_path is not None)
|
||||||
|
and
|
||||||
|
(record["name"] == (domain_path + "." + domain_base))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
and
|
||||||
|
(
|
||||||
|
(type_ is None)
|
||||||
|
or
|
||||||
|
(record["type"] == type_)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
info["record"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for entry in matching:
|
||||||
|
id_ = entry["id"]
|
||||||
|
result = api_call(
|
||||||
|
environment,
|
||||||
|
accesstoken,
|
||||||
|
"nameserver",
|
||||||
|
"deleteRecord",
|
||||||
|
{
|
||||||
|
"id": id_,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
api_macro_logout(environment, accesstoken)
|
||||||
|
|
||||||
|
|
||||||
|
def main(
|
||||||
|
):
|
||||||
|
## args
|
||||||
|
argument_parser = _argparse.ArgumentParser(
|
||||||
description = "INWX CLI Frontend"
|
description = "INWX CLI Frontend"
|
||||||
)
|
)
|
||||||
argumentparser.add_argument(
|
argument_parser.add_argument(
|
||||||
"-c",
|
"-c",
|
||||||
"--conf",
|
"--conf",
|
||||||
|
type = str,
|
||||||
dest = "conf",
|
dest = "conf",
|
||||||
default = _os.path.join(str(_pathlib.Path.home()), ".inwx-conf.json"),
|
default = _os.path.join(str(_pathlib.Path.home()), ".inwx-conf.json"),
|
||||||
metavar = "<conf>",
|
metavar = "<conf>",
|
||||||
help = "path to configuration file",
|
help = "path to configuration file",
|
||||||
)
|
)
|
||||||
argumentparser.add_argument(
|
argument_parser.add_argument(
|
||||||
"-e",
|
"-e",
|
||||||
"--environment",
|
"--environment",
|
||||||
|
type = str,
|
||||||
dest = "environment",
|
dest = "environment",
|
||||||
metavar = "<environment>",
|
metavar = "<environment>",
|
||||||
default = None,
|
default = None,
|
||||||
help = "environment to use; one of the keys in the 'url' filed of the configuration; overwrites the configuration value",
|
help = "environment to use; one of the keys in the 'url' file of the configuration; overwrites the configuration value",
|
||||||
)
|
)
|
||||||
argumentparser.add_argument(
|
argument_parser.add_argument(
|
||||||
"-u",
|
"-u",
|
||||||
"--username",
|
"--username",
|
||||||
|
type = str,
|
||||||
dest = "username",
|
dest = "username",
|
||||||
metavar = "<username>",
|
metavar = "<username>",
|
||||||
default = None,
|
default = None,
|
||||||
help = "username; overwrites the configuration value",
|
help = "username; overwrites the configuration value",
|
||||||
)
|
)
|
||||||
argumentparser.add_argument(
|
argument_parser.add_argument(
|
||||||
"-p",
|
"-p",
|
||||||
"--password",
|
"--password",
|
||||||
|
type = str,
|
||||||
dest = "password",
|
dest = "password",
|
||||||
metavar = "<password>",
|
metavar = "<password>",
|
||||||
default = None,
|
default = None,
|
||||||
help = "password; overwrites the configuration value",
|
help = "password; overwrites the configuration value",
|
||||||
)
|
)
|
||||||
'''
|
argument_parser.add_argument(
|
||||||
argumentparser.add_argument(
|
|
||||||
"-d",
|
"-d",
|
||||||
"--domain",
|
"--domain",
|
||||||
|
type = str,
|
||||||
dest = "domain",
|
dest = "domain",
|
||||||
default = None,
|
default = None,
|
||||||
metavar = "<domain>",
|
metavar = "<domain>",
|
||||||
help = "the domain to work with"
|
help = "the domain to work with"
|
||||||
)
|
)
|
||||||
'''
|
argument_parser.add_argument(
|
||||||
argumentparser.add_argument(
|
"-t",
|
||||||
|
"--type",
|
||||||
|
type = str,
|
||||||
|
dest = "type",
|
||||||
|
default = None,
|
||||||
|
metavar = "<type>",
|
||||||
|
help = "the record type (A, AAAA, TXT, …)"
|
||||||
|
)
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"-v",
|
||||||
|
"--value",
|
||||||
|
type = str,
|
||||||
|
dest = "value",
|
||||||
|
default = None,
|
||||||
|
metavar = "<value>",
|
||||||
|
help = "value for the record"
|
||||||
|
)
|
||||||
|
argument_parser.add_argument(
|
||||||
"-x",
|
"-x",
|
||||||
"--challenge-prefix",
|
"--challenge-prefix",
|
||||||
|
type = str,
|
||||||
dest = "challenge_prefix",
|
dest = "challenge_prefix",
|
||||||
metavar = "<challenge-prefix>",
|
metavar = "<challenge-prefix>",
|
||||||
default = "_acme-challenge",
|
default = "_acme-challenge",
|
||||||
help = "which subdomain to use for ACME challanges",
|
help = "which subdomain to use for ACME challanges",
|
||||||
)
|
)
|
||||||
argumentparser.add_argument(
|
argument_parser.add_argument(
|
||||||
"-w",
|
"-w",
|
||||||
"--delay",
|
"--delay",
|
||||||
dest = "delay",
|
|
||||||
type = float,
|
type = float,
|
||||||
|
dest = "delay",
|
||||||
default = 60.0,
|
default = 60.0,
|
||||||
metavar = "<delay>",
|
metavar = "<delay>",
|
||||||
help = "seconds to wait at end of certbot auth hook",
|
help = "seconds to wait at end of certbot auth hook",
|
||||||
)
|
)
|
||||||
argumentparser.add_argument(
|
argument_parser.add_argument(
|
||||||
"action",
|
|
||||||
type = str,
|
type = str,
|
||||||
choices = ["info", "list", "save", "certbot-hook"],
|
dest = "action",
|
||||||
|
choices = [
|
||||||
|
"conf-schema",
|
||||||
|
"info",
|
||||||
|
"list",
|
||||||
|
"save",
|
||||||
|
"delete",
|
||||||
|
"certbot-hook",
|
||||||
|
],
|
||||||
metavar = "<action>",
|
metavar = "<action>",
|
||||||
help = "action to execute",
|
help = string_coin(
|
||||||
|
"action to execute; options:\n{{options}}",
|
||||||
|
{
|
||||||
|
"options": convey(
|
||||||
|
[
|
||||||
|
{"name": "conf-schema", "requirements": []},
|
||||||
|
{"name": "info", "requirements": []},
|
||||||
|
{"name": "list", "requirements": ["<domain>"]},
|
||||||
|
{"name": "save", "requirements": ["<domain>", "<type>", "<value>"]},
|
||||||
|
{"name": "delete", "requirements": ["<domain>"]},
|
||||||
|
{"name": "certbot-hook", "requirements": []},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
lambda x: map(
|
||||||
|
lambda entry: string_coin(
|
||||||
|
"{{name}}{{macro_requirements}}",
|
||||||
|
{
|
||||||
|
"name": entry["name"],
|
||||||
|
"macro_requirements": (
|
||||||
|
""
|
||||||
|
if (len(entry["requirements"]) <= 0) else
|
||||||
|
string_coin(
|
||||||
|
" (requires: {{requirements}})",
|
||||||
|
{
|
||||||
|
"requirements": ",".join(entry["requirements"]),
|
||||||
|
}
|
||||||
)
|
)
|
||||||
argumentparser.add_argument(
|
),
|
||||||
"parameter",
|
}
|
||||||
nargs = "*",
|
),
|
||||||
type = str,
|
x
|
||||||
metavar = "<parameters>",
|
),
|
||||||
help = "action specific parameters",
|
" | ".join,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
arguments = argumentparser.parse_args()
|
}
|
||||||
return arguments
|
),
|
||||||
|
)
|
||||||
|
args = argument_parser.parse_args()
|
||||||
|
|
||||||
|
## conf
|
||||||
|
conf_load(args.conf)
|
||||||
|
|
||||||
def main(
|
## vars
|
||||||
):
|
environment = (args.environment or conf_get("environment"))
|
||||||
arguments = args()
|
account_username = (args.username or conf_get("account.username"))
|
||||||
|
account_password = (args.password or conf_get("account.password"))
|
||||||
|
domain_parts = (None if (args.domain is None) else args.domain.split("."))
|
||||||
|
domain_base = (None if (domain_parts is None) else ".".join(domain_parts[-2:]))
|
||||||
|
domain_path = (None if ((domain_parts is None) or (len(domain_parts[:-2]) <= 0)) else ".".join(domain_parts[:-2]))
|
||||||
|
|
||||||
conf_load(arguments.conf)
|
## exec
|
||||||
if (not (arguments.environment is None)): conf_set("environment", arguments.environment)
|
if (args.action == "conf-schema"):
|
||||||
if (not (arguments.username is None)): conf_set("account.username", arguments.username)
|
print(_json.dumps(conf_schema(), indent = "\t"))
|
||||||
if (not (arguments.password is None)): conf_set("account.password", arguments.password)
|
elif (args.action == "info"):
|
||||||
|
if (account_username is None):
|
||||||
if (arguments.action == "info"):
|
raise ValueError("account username required")
|
||||||
|
else:
|
||||||
|
if (account_password is None):
|
||||||
|
raise ValueError("account password required")
|
||||||
|
else:
|
||||||
result = api_macro_info(
|
result = api_macro_info(
|
||||||
conf_get("environment"),
|
environment,
|
||||||
conf_get("account.username"),
|
account_username,
|
||||||
conf_get("account.password")
|
account_password
|
||||||
)
|
)
|
||||||
print(_json.dumps(result, indent = "\t"))
|
print(_json.dumps(result, indent = "\t"))
|
||||||
elif (arguments.action == "list"):
|
elif (args.action == "list"):
|
||||||
domain = arguments.parameter[0]
|
if (account_username is None):
|
||||||
|
raise ValueError("account username required")
|
||||||
|
else:
|
||||||
|
if (account_password is None):
|
||||||
|
raise ValueError("account password required")
|
||||||
|
else:
|
||||||
|
if (args.domain_base is None):
|
||||||
|
raise ValueError("domain base required")
|
||||||
|
else:
|
||||||
result = api_macro_list(
|
result = api_macro_list(
|
||||||
conf_get("environment"),
|
environment,
|
||||||
conf_get("account.username"),
|
account_username,
|
||||||
conf_get("account.password"),
|
account_password,
|
||||||
domain
|
domain_base
|
||||||
)
|
)
|
||||||
print(_json.dumps(result, indent = "\t"))
|
print(_json.dumps(result, indent = "\t"))
|
||||||
elif (arguments.action == "save"):
|
elif (args.action == "save"):
|
||||||
domain = arguments.parameter[0]
|
if (account_username is None):
|
||||||
name = arguments.parameter[1]
|
raise ValueError("account username required")
|
||||||
type_ = arguments.parameter[2]
|
else:
|
||||||
content = arguments.parameter[3]
|
if (account_password is None):
|
||||||
|
raise ValueError("account password required")
|
||||||
|
else:
|
||||||
|
if (args.domain is None):
|
||||||
|
raise ValueError("domain required")
|
||||||
|
else:
|
||||||
|
if (args.type is None):
|
||||||
|
raise ValueError("type required")
|
||||||
|
else:
|
||||||
|
if (args.value is None):
|
||||||
|
raise ValueError("value required")
|
||||||
|
else:
|
||||||
api_macro_save(
|
api_macro_save(
|
||||||
conf_get("environment"),
|
environment,
|
||||||
conf_get("account.username"),
|
account_username,
|
||||||
conf_get("account.password"),
|
account_password,
|
||||||
domain,
|
domain_base,
|
||||||
name,
|
domain_path,
|
||||||
type_,
|
args.type,
|
||||||
content
|
args.value
|
||||||
)
|
)
|
||||||
# print(_json.dumps(result, indent = "\t"))
|
elif (args.action == "delete"):
|
||||||
elif (arguments.action == "certbot-hook"):
|
if (account_username is None):
|
||||||
|
raise ValueError("account username required")
|
||||||
|
else:
|
||||||
|
if (account_password is None):
|
||||||
|
raise ValueError("account password required")
|
||||||
|
else:
|
||||||
|
if (args.domain is None):
|
||||||
|
raise ValueError("domain required")
|
||||||
|
else:
|
||||||
|
api_macro_delete(
|
||||||
|
environment,
|
||||||
|
account_username,
|
||||||
|
account_password,
|
||||||
|
domain_base,
|
||||||
|
domain_path,
|
||||||
|
args.type
|
||||||
|
)
|
||||||
|
elif (args.action == "certbot-hook"):
|
||||||
|
if (account_username is None):
|
||||||
|
raise ValueError("account username required")
|
||||||
|
else:
|
||||||
|
if (account_password is None):
|
||||||
|
raise ValueError("account password required")
|
||||||
|
else:
|
||||||
domain_full_parts = _os.environ["CERTBOT_DOMAIN"].split(".")
|
domain_full_parts = _os.environ["CERTBOT_DOMAIN"].split(".")
|
||||||
account = ".".join(domain_full_parts[-2:])
|
domain_base = ".".join(domain_full_parts[-2:])
|
||||||
concern = ".".join(domain_full_parts[:-2])
|
domain_path_stripped = ".".join(domain_full_parts[:-2])
|
||||||
domain = account
|
domain_path = (args.challenge_prefix + "." + domain_path_stripped)
|
||||||
name = (arguments.challenge_prefix + "." + concern)
|
|
||||||
type_ = "TXT"
|
type_ = "TXT"
|
||||||
content = _os.environ["CERTBOT_VALIDATION"]
|
content = _os.environ["CERTBOT_VALIDATION"]
|
||||||
api_macro_save(
|
api_macro_save(
|
||||||
conf_get("environment"),
|
environment,
|
||||||
conf_get("account.username"),
|
account_username,
|
||||||
conf_get("account.password"),
|
account_password,
|
||||||
domain,
|
domain_base,
|
||||||
name,
|
domain_path,
|
||||||
type_,
|
type_,
|
||||||
content
|
content
|
||||||
)
|
)
|
||||||
_time.sleep(arguments.delay)
|
_time.sleep(args.delay)
|
||||||
# print(_json.dumps(result, indent = "\t"))
|
# print(_json.dumps(result, indent = "\t"))
|
||||||
else:
|
else:
|
||||||
log("unhandled action '%s'" % (arguments.action, ))
|
log("unhandled action '%s'" % (args.action, ))
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except ValueError as error:
|
except ValueError as error:
|
||||||
_sys.stderr.write(str(error) + "\n")
|
_sys.stderr.write("-- %s\n" % str(error))
|
||||||
|
|
||||||
|
|
|
||||||
133
roles/tlscert_acme_inwx/files/tls-get
Executable file
133
roles/tlscert_acme_inwx/files/tls-get
Executable file
|
|
@ -0,0 +1,133 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import sys as _sys
|
||||||
|
import os as _os
|
||||||
|
import json as _json
|
||||||
|
import pathlib as _pathlib
|
||||||
|
import argparse as _argparse
|
||||||
|
|
||||||
|
|
||||||
|
def file_read(path):
|
||||||
|
handle = open(path, "r")
|
||||||
|
content = handle.read()
|
||||||
|
handle.close()
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
## args
|
||||||
|
argument_parser = _argparse.ArgumentParser()
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--conf-path",
|
||||||
|
type = str,
|
||||||
|
dest = "conf_path",
|
||||||
|
metavar = "<conf-path>",
|
||||||
|
default = _os.path.join(str(_pathlib.Path.home()), ".tls-get-conf.json"),
|
||||||
|
)
|
||||||
|
argument_parser.add_argument(
|
||||||
|
dest = "domain",
|
||||||
|
metavar = "<domain>",
|
||||||
|
help = "the domain for which the TLS certificate shall be generated"
|
||||||
|
)
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--target-directory",
|
||||||
|
dest = "target_directory",
|
||||||
|
type = str,
|
||||||
|
metavar = "<target-directory>",
|
||||||
|
default = "/etc/ssl",
|
||||||
|
)
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"-x",
|
||||||
|
"--challenge-prefix",
|
||||||
|
dest = "challenge_prefix",
|
||||||
|
type = str,
|
||||||
|
metavar = "<challenge-prefix>",
|
||||||
|
default = "_acme-challenge",
|
||||||
|
help = "which subdomain to use for ACME challanges",
|
||||||
|
)
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"-w",
|
||||||
|
"--delay",
|
||||||
|
dest = "delay",
|
||||||
|
type = float,
|
||||||
|
default = 60.0,
|
||||||
|
metavar = "<delay>",
|
||||||
|
help = "seconds to wait at end of certbot auth hook",
|
||||||
|
)
|
||||||
|
argument_parser.add_argument(
|
||||||
|
"-q",
|
||||||
|
"--dry-run",
|
||||||
|
dest = "dry_run",
|
||||||
|
action = "store_true",
|
||||||
|
default = False,
|
||||||
|
help = "whether to only print the command on stdout instead of executing it",
|
||||||
|
)
|
||||||
|
args = argument_parser.parse_args()
|
||||||
|
|
||||||
|
## vars
|
||||||
|
conf = _json.loads(file_read(args.conf_path))
|
||||||
|
le_dir = "/etc/letsencrypt/live"
|
||||||
|
|
||||||
|
## exec
|
||||||
|
command_certbot = " ".join(
|
||||||
|
[
|
||||||
|
"certbot",
|
||||||
|
"certonly",
|
||||||
|
("--email='%s'" % conf["acme_account"]["email"]),
|
||||||
|
# ("--work-dir='%s'" % conf["misc"]["working_directory"]),
|
||||||
|
"--preferred-challenges='dns'",
|
||||||
|
"--non-interactive",
|
||||||
|
"--agree-tos",
|
||||||
|
("--domain='%s'" % args.domain),
|
||||||
|
"--manual",
|
||||||
|
(
|
||||||
|
"--manual-auth-hook='%s'"
|
||||||
|
% " ".join(
|
||||||
|
[
|
||||||
|
"/usr/local/bin/inwx",
|
||||||
|
("--username=\"%s\"" % conf["inwx_account"]["username"]),
|
||||||
|
("--password=\"%s\"" % conf["inwx_account"]["password"]),
|
||||||
|
"certbot-hook",
|
||||||
|
("--delay=%.4f" % args.delay),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"--post-hook='%s'"
|
||||||
|
% " ".join(
|
||||||
|
[
|
||||||
|
"/usr/local/bin/inwx",
|
||||||
|
("--username=\"%s\"" % conf["inwx_account"]["username"]),
|
||||||
|
("--password=\"%s\"" % conf["inwx_account"]["password"]),
|
||||||
|
"delete",
|
||||||
|
("--domain=\"%s\"" % (args.challenge_prefix + "." + args.domain)),
|
||||||
|
("--type=\"TXT\""),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if (args.dry_run):
|
||||||
|
_sys.stdout.write(command_certbot + "\n")
|
||||||
|
else:
|
||||||
|
_os.system(command_certbot)
|
||||||
|
subjects = [
|
||||||
|
{"source_name": "privkey", "target_directory": "private"},
|
||||||
|
{"source_name": "cert", "target_directory": "certs"},
|
||||||
|
{"source_name": "chain", "target_directory": "chains"},
|
||||||
|
{"source_name": "fullchain", "target_directory": "fullchains"},
|
||||||
|
]
|
||||||
|
for subject in subjects:
|
||||||
|
_os.system(
|
||||||
|
"mkdir --parents %s && cp --dereference %s %s"
|
||||||
|
% (
|
||||||
|
_os.path.join(args.target_directory, subject["target_directory"]),
|
||||||
|
_os.path.join(le_dir, args.domain, "%s.pem" % subject["source_name"]),
|
||||||
|
_os.path.join(args.target_directory, subject["target_directory"], "%s.pem" % args.domain),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
@ -1,3 +1,8 @@
|
||||||
|
## Beschreibung
|
||||||
|
|
||||||
|
- richtet die regelmäßige TLS-Zertifikats-Erstellung für eine Domäne und führt eine Erstellung direkt aus
|
||||||
|
|
||||||
|
|
||||||
## Verweise
|
## Verweise
|
||||||
|
|
||||||
- [Digital Ocean | How To Acquire a Let's Encrypt Certificate Using Ansible](https://www.digitalocean.com/community/tutorials/how-to-acquire-a-let-s-encrypt-certificate-using-ansible-on-ubuntu-18-04)
|
- [Digital Ocean | How To Acquire a Let's Encrypt Certificate Using Ansible](https://www.digitalocean.com/community/tutorials/how-to-acquire-a-let-s-encrypt-certificate-using-ansible-on-ubuntu-18-04)
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,19 @@
|
||||||
"update_cache": true,
|
"update_cache": true,
|
||||||
"pkg": [
|
"pkg": [
|
||||||
"openssl",
|
"openssl",
|
||||||
"python3-cryptography"
|
"python3-cryptography",
|
||||||
|
"certbot"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "directories | ssl",
|
"name": "directories",
|
||||||
"become": true,
|
"become": true,
|
||||||
"loop": [
|
"loop": [
|
||||||
"{{var_tlscert_acme_inwx_ssl_directory}}/private",
|
"{{var_tlscert_acme_inwx_ssl_directory}}/private",
|
||||||
"{{var_tlscert_acme_inwx_ssl_directory}}/csr",
|
"{{var_tlscert_acme_inwx_ssl_directory}}/csr",
|
||||||
"{{var_tlscert_acme_inwx_ssl_directory}}/certs",
|
"{{var_tlscert_acme_inwx_ssl_directory}}/certs",
|
||||||
|
"{{var_tlscert_acme_inwx_ssl_directory}}/chains",
|
||||||
"{{var_tlscert_acme_inwx_ssl_directory}}/fullchains"
|
"{{var_tlscert_acme_inwx_ssl_directory}}/fullchains"
|
||||||
],
|
],
|
||||||
"ansible.builtin.file": {
|
"ansible.builtin.file": {
|
||||||
|
|
@ -25,54 +27,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "directories | Let's Encrypt account key",
|
"name": "tools | inwx",
|
||||||
"become": true,
|
|
||||||
"ansible.builtin.file": {
|
|
||||||
"state": "directory",
|
|
||||||
"path": "{{var_tlscert_acme_inwx_acme_account_key_path | dirname}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "key",
|
|
||||||
"become": true,
|
|
||||||
"community.crypto.openssl_privatekey": {
|
|
||||||
"path": "{{var_tlscert_acme_inwx_ssl_directory}}/private/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "csr",
|
|
||||||
"become": true,
|
|
||||||
"community.crypto.openssl_csr": {
|
|
||||||
"common_name": "{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}",
|
|
||||||
"privatekey_path": "{{var_tlscert_acme_inwx_ssl_directory}}/private/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem",
|
|
||||||
"path": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "acme | account key",
|
|
||||||
"become": true,
|
|
||||||
"ansible.builtin.shell": {
|
|
||||||
"cmd": "test -f {{var_tlscert_acme_inwx_acme_account_key_path}} || openssl genrsa 4096 > {{var_tlscert_acme_inwx_acme_account_key_path}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "acme | init",
|
|
||||||
"become": true,
|
|
||||||
"community.crypto.acme_certificate": {
|
|
||||||
"acme_version": 2,
|
|
||||||
"acme_directory": "https://acme-v02.api.letsencrypt.org/directory",
|
|
||||||
"account_email": "{{var_tlscert_acme_inwx_acme_account_email}}",
|
|
||||||
"account_key_src": "{{var_tlscert_acme_inwx_acme_account_key_path}}",
|
|
||||||
"terms_agreed": true,
|
|
||||||
"csr": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem",
|
|
||||||
"challenge": "dns-01",
|
|
||||||
"dest": "{{var_tlscert_acme_inwx_ssl_directory}}/certs/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem",
|
|
||||||
"fullchain_dest": "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem"
|
|
||||||
},
|
|
||||||
"register": "temp_acme_data"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dns challenge | place script",
|
|
||||||
"become": true,
|
"become": true,
|
||||||
"ansible.builtin.copy": {
|
"ansible.builtin.copy": {
|
||||||
"src": "inwx",
|
"src": "inwx",
|
||||||
|
|
@ -81,33 +36,60 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dns challenge | execute",
|
"name": "tools | tls-get | script",
|
||||||
"when": "'challenge_data' in temp_acme_data",
|
|
||||||
"ansible.builtin.command": {
|
|
||||||
"cmd": "/usr/local/bin/inwx --username={{var_tlscert_acme_inwx_inwx_account_username}} --password={{var_tlscert_acme_inwx_inwx_account_password}} save {{var_tlscert_acme_inwx_domain_base}} _acme-challenge.{{var_tlscert_acme_inwx_domain_path}} TXT {{temp_acme_data['challenge_data'][var_tlscert_acme_inwx_domain_path + '.' + var_tlscert_acme_inwx_domain_base]['dns-01']['resource_value']}}"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dns challenge | wait",
|
|
||||||
"when": "'challenge_data' in temp_acme_data",
|
|
||||||
"ansible.builtin.pause": {
|
|
||||||
"seconds": 60
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "acme | finalize",
|
|
||||||
"become": true,
|
"become": true,
|
||||||
"community.crypto.acme_certificate": {
|
"ansible.builtin.file": {
|
||||||
"acme_version": 2,
|
"src": "tls-get",
|
||||||
"acme_directory": "https://acme-v02.api.letsencrypt.org/directory",
|
"dest": "/usr/local/bin/tls-get",
|
||||||
"account_email": "{{var_tlscert_acme_inwx_acme_account_email}}",
|
"mode": "a+x"
|
||||||
"account_key_src": "{{var_tlscert_acme_inwx_acme_account_key_path}}",
|
}
|
||||||
"terms_agreed": true,
|
},
|
||||||
"csr": "{{var_tlscert_acme_inwx_ssl_directory}}/csr/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem",
|
{
|
||||||
"challenge": "dns-01",
|
"name": "tools | tls-get | conf",
|
||||||
"dest": "{{var_tlscert_acme_inwx_ssl_directory}}/certs/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem",
|
"become": true,
|
||||||
"fullchain_dest": "{{var_tlscert_acme_inwx_ssl_directory}}/fullchains/{{var_tlscert_acme_inwx_domain_path}}.{{var_tlscert_acme_inwx_domain_base}}.pem",
|
"ansible.builtin.template": {
|
||||||
"data": "{{temp_acme_data}}"
|
"src": "tls-get-conf.json.j2",
|
||||||
|
"dest": "/root/.tls-get-conf.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tools | pseudo queue | setup",
|
||||||
|
"become": true,
|
||||||
|
"ansible.builtin.cron": {
|
||||||
|
"state": "present",
|
||||||
|
"disabled": false,
|
||||||
|
"name": "pseudo queue",
|
||||||
|
"special_time": "reboot",
|
||||||
|
"job": "bash -c \"test -p /var/pseudoqueue || (mkfifo --mode=0600 /var/pseudoqueue && (while true ; do bash < /var/pseudoqueue ; done))\""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tools | pseudo queue | run",
|
||||||
|
"become": true,
|
||||||
|
"ansible.builtin.shell": {
|
||||||
|
"cmd": "bash -c \"test -p /var/pseudoqueue || (mkfifo --mode=0600 /var/pseudoqueue && (while true ; do bash < /var/pseudoqueue ; done))\""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setup auto renewal",
|
||||||
|
"become": true,
|
||||||
|
"ansible.builtin.cron": {
|
||||||
|
"state": "present",
|
||||||
|
"disabled": false,
|
||||||
|
"name": "TLS certificate for {{var_tlscert_acme_inwx_domain}}",
|
||||||
|
"minute": "0",
|
||||||
|
"hour": "2",
|
||||||
|
"day": "1",
|
||||||
|
"month": "*",
|
||||||
|
"weekday": "*",
|
||||||
|
"job": "echo '/usr/local/bin/tls-get {{var_tlscert_acme_inwx_domain}} --conf-path=/root/.tls-get-conf.json --target-directory={{var_tlscert_acme_inwx_ssl_directory}}' > /var/pseudoqueue"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "run",
|
||||||
|
"become": true,
|
||||||
|
"ansible.builtin.shell": {
|
||||||
|
"cmd": "echo '/usr/local/bin/tls-get {{var_tlscert_acme_inwx_domain}} --conf-path=/root/.tls-get-conf.json --target-directory={{var_tlscert_acme_inwx_ssl_directory}}' > /var/pseudoqueue"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
12
roles/tlscert_acme_inwx/templates/tls-get-conf.json.j2
Normal file
12
roles/tlscert_acme_inwx/templates/tls-get-conf.json.j2
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"acme_account": {
|
||||||
|
"email": "{{var_tlscert_acme_inwx_acme_account_email}}"
|
||||||
|
},
|
||||||
|
"inwx_account": {
|
||||||
|
"username": "{{var_tlscert_acme_inwx_inwx_account_username}}",
|
||||||
|
"password": "{{var_tlscert_acme_inwx_inwx_account_password}}"
|
||||||
|
},
|
||||||
|
"misc": {
|
||||||
|
"working_directory": "/tmp/acme"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue