Early support for auditlog
This commit is contained in:
parent
3787ad12df
commit
c26a46e9a4
2 changed files with 110 additions and 43 deletions
|
@ -7,7 +7,7 @@ import os
|
|||
import sys
|
||||
import urllib.parse
|
||||
from os import environ, mkdir
|
||||
from os.path import isdir, isfile, join
|
||||
from os.path import isdir, isfile, join, split
|
||||
from typing import Union
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
@ -16,6 +16,7 @@ import requests
|
|||
import yaml
|
||||
from requests.models import HTTPBasicAuth
|
||||
from simplejson.errors import JSONDecodeError as SimplejsonJSONDecodeError
|
||||
|
||||
try:
|
||||
from requests.exceptions import JSONDecodeError as RequestsJSONDecodeError
|
||||
except ImportError:
|
||||
|
@ -45,8 +46,7 @@ def nested_out(input, tabs="") -> str:
|
|||
string += "{}\n".format(input)
|
||||
elif isinstance(input, dict):
|
||||
for key, value in input.items():
|
||||
string += "{}{}: {}".format(tabs, key,
|
||||
nested_out(value, tabs + " "))
|
||||
string += "{}{}: {}".format(tabs, key, nested_out(value, tabs + " "))
|
||||
elif isinstance(input, list):
|
||||
for entry in input:
|
||||
string += "{}\n{}".format(tabs, nested_out(entry, tabs + " "))
|
||||
|
@ -70,15 +70,57 @@ def run_add(url: str, jsonout: bool, headers: dict):
|
|||
out = response.json()
|
||||
if isinstance(out, list):
|
||||
for record in out:
|
||||
if (record["data"] == parsed["data"]
|
||||
if (
|
||||
record["data"] == parsed["data"]
|
||||
and record["name"] == parsed["name"]
|
||||
and record["rtype"] == parsed["rtype"]):
|
||||
and record["rtype"] == parsed["rtype"]
|
||||
):
|
||||
output(record, jsonout)
|
||||
break
|
||||
else:
|
||||
output(out, jsonout)
|
||||
|
||||
|
||||
def run_audit(url: str, jsonout: bool, headers: dict):
|
||||
response = requests.get(url, headers=headers)
|
||||
string = response.content.decode("utf-8")
|
||||
if jsonout:
|
||||
out = []
|
||||
lines = string.splitlines()
|
||||
index = 0
|
||||
text = ""
|
||||
timestamp = ""
|
||||
while index < len(lines):
|
||||
line = lines[index]
|
||||
index += 1
|
||||
cur_has_timestamp = line.startswith("[")
|
||||
next_has_timestamp = index < len(lines) and lines[index].startswith(
|
||||
"["
|
||||
)
|
||||
# Simple case, just one line with timestamp
|
||||
if cur_has_timestamp and next_has_timestamp:
|
||||
text = ":". join(line.split(':')[1:])
|
||||
timestamp = line.split(']')[0].split('[')[1]
|
||||
out.append({'timestamp': timestamp, 'text': text})
|
||||
text = ""
|
||||
timestamp = ""
|
||||
elif cur_has_timestamp:
|
||||
timestamp = line.split(']')[0].split('[')[1]
|
||||
text = ":". join(line.split(':')[1:])
|
||||
elif next_has_timestamp:
|
||||
text += f'\n{line}'
|
||||
out.append({'timestamp': timestamp, 'text': text})
|
||||
text = ""
|
||||
timestamp = ""
|
||||
else:
|
||||
text += f'\n{line}'
|
||||
|
||||
else:
|
||||
out = string
|
||||
|
||||
output(out, jsonout)
|
||||
|
||||
|
||||
def run_complete(shell: Union[None, str]):
|
||||
if not shell or shell in ["bash", "zsh"]:
|
||||
os.system("register-python-argcomplete knotctl")
|
||||
|
@ -98,7 +140,7 @@ def run_config(
|
|||
config = {"baseurl": baseurl, "username": username, "password": password}
|
||||
needed = []
|
||||
if context:
|
||||
symlink = f'{config_filename}-{context}'
|
||||
symlink = f"{config_filename}-{context}"
|
||||
found = os.path.isfile(symlink)
|
||||
if os.path.islink(config_filename):
|
||||
os.remove(config_filename)
|
||||
|
@ -118,7 +160,8 @@ def run_config(
|
|||
error(
|
||||
"Can not configure without {}".format(need),
|
||||
"No {}".format(need),
|
||||
))
|
||||
)
|
||||
)
|
||||
sys.exit(1)
|
||||
config[need] = input("Enter {}: ".format(need))
|
||||
|
||||
|
@ -142,10 +185,7 @@ def run_delete(url: str, jsonout: bool, headers: dict):
|
|||
output(reply, jsonout)
|
||||
|
||||
|
||||
def run_list(url: str,
|
||||
jsonout: bool,
|
||||
headers: dict,
|
||||
ret=False) -> Union[None, str]:
|
||||
def run_list(url: str, jsonout: bool, headers: dict, ret=False) -> Union[None, str]:
|
||||
response = requests.get(url, headers=headers)
|
||||
string = response.json()
|
||||
if ret:
|
||||
|
@ -176,7 +216,7 @@ def setup_url(
|
|||
url += "/{}".format(zone)
|
||||
if name and zone:
|
||||
if name.endswith(zone.rstrip(".")):
|
||||
name += '.'
|
||||
name += "."
|
||||
url += "/records/{}".format(name)
|
||||
if zone and name and rtype:
|
||||
url += "/{}".format(rtype)
|
||||
|
@ -197,14 +237,16 @@ def setup_url(
|
|||
error(
|
||||
"ttl only makes sense with rtype, name and zone",
|
||||
"Missing parameter",
|
||||
))
|
||||
)
|
||||
)
|
||||
sys.exit(1)
|
||||
if rtype and (not name or not zone):
|
||||
output(
|
||||
error(
|
||||
"rtype only makes sense with name and zone",
|
||||
"Missing parameter",
|
||||
))
|
||||
)
|
||||
)
|
||||
sys.exit(1)
|
||||
if name and not zone:
|
||||
output(error("name only makes sense with a zone", "Missing parameter"))
|
||||
|
@ -293,7 +335,11 @@ def main() -> int:
|
|||
* https://en.wikipedia.org/wiki/Zone_file
|
||||
"""
|
||||
# Grab user input
|
||||
parser = argparse.ArgumentParser(description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser = argparse.ArgumentParser(
|
||||
description=description,
|
||||
epilog=epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument("--json", action=argparse.BooleanOptionalAction)
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
|
||||
|
@ -305,6 +351,9 @@ def main() -> int:
|
|||
addcmd.add_argument("-t", "--ttl")
|
||||
addcmd.add_argument("-z", "--zone", required=True)
|
||||
|
||||
audit_description = "Audit the log file for errors."
|
||||
subparsers.add_parser("audit", description=audit_description)
|
||||
|
||||
complete_description = "Generate shell completion script."
|
||||
completecmd = subparsers.add_parser("completion", description=complete_description)
|
||||
completecmd.add_argument("-s", "--shell")
|
||||
|
@ -330,24 +379,34 @@ def main() -> int:
|
|||
listcmd.add_argument("-r", "--rtype")
|
||||
listcmd.add_argument("-z", "--zone", required=True)
|
||||
|
||||
update_description = "Update a record in the zone. The record must exist in the zone.\n"
|
||||
update_description += "In this case --data, --name, --rtype and --ttl switches are used\n"
|
||||
update_description += "for searching for the appropriate record, while the --argument\n"
|
||||
update_description = (
|
||||
"Update a record in the zone. The record must exist in the zone.\n"
|
||||
)
|
||||
update_description += (
|
||||
"In this case --data, --name, --rtype and --ttl switches are used\n"
|
||||
)
|
||||
update_description += (
|
||||
"for searching for the appropriate record, while the --argument\n"
|
||||
)
|
||||
update_description += "switches are used for updating the record."
|
||||
update_epilog = """Available arguments are:
|
||||
data: New record data.
|
||||
name: New record domain name.
|
||||
rtype: New record type.
|
||||
ttl: New record time to live (TTL)."""
|
||||
updatecmd = subparsers.add_parser("update", description=update_description, epilog=update_epilog, formatter_class=argparse.RawDescriptionHelpFormatter )
|
||||
updatecmd = subparsers.add_parser(
|
||||
"update",
|
||||
description=update_description,
|
||||
epilog=update_epilog,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
updatecmd.add_argument(
|
||||
"-a",
|
||||
"--argument",
|
||||
nargs="*",
|
||||
action="append",
|
||||
metavar="KEY=VALUE",
|
||||
help=
|
||||
"Specify key - value pairs to be updated: name=dns1.example.com. or data=127.0.0.1 for example. --argument can be repeated",
|
||||
help="Specify key - value pairs to be updated: name=dns1.example.com. or data=127.0.0.1 for example. --argument can be repeated",
|
||||
required=True,
|
||||
)
|
||||
updatecmd.add_argument("-d", "--data", required=True)
|
||||
|
@ -370,8 +429,9 @@ def main() -> int:
|
|||
mkdir(config_basepath)
|
||||
|
||||
if args.command == "config":
|
||||
run_config(config_filename, args.context, args.baseurl, args.username,
|
||||
args.password)
|
||||
run_config(
|
||||
config_filename, args.context, args.baseurl, args.username, args.password
|
||||
)
|
||||
return 0
|
||||
|
||||
if not isfile(config_filename):
|
||||
|
@ -392,8 +452,7 @@ def main() -> int:
|
|||
output(response.json())
|
||||
return 1
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
output(
|
||||
error("Could not decode api response as JSON", "Could not decode"))
|
||||
output(error("Could not decode api response as JSON", "Could not decode"))
|
||||
return 1
|
||||
headers = {"Authorization": "Bearer {}".format(token)}
|
||||
|
||||
|
@ -411,7 +470,9 @@ def main() -> int:
|
|||
soa_url = setup_url(baseurl, None, None, zname, "SOA", None, args.zone)
|
||||
soa_json = run_list(soa_url, True, headers, ret=True)
|
||||
ttl = soa_json[0]["ttl"]
|
||||
|
||||
if args.command == "audit":
|
||||
url = baseurl + "/user/auditlog"
|
||||
else:
|
||||
try:
|
||||
url = setup_url(
|
||||
baseurl,
|
||||
|
@ -435,9 +496,15 @@ def main() -> int:
|
|||
run_list(url, args.json, headers)
|
||||
elif args.command == "update":
|
||||
run_update(url, args.json, headers)
|
||||
elif args.command == "audit":
|
||||
run_audit(url, args.json, headers)
|
||||
else:
|
||||
parser.print_help(sys.stderr)
|
||||
return 1
|
||||
except requests.exceptions.RequestException as e:
|
||||
output(error(e, "Could not connect to server"))
|
||||
except (RequestsJSONDecodeError, SimplejsonJSONDecodeError):
|
||||
output(
|
||||
error("Could not decode api response as JSON", "Could not decode"))
|
||||
output(error("Could not decode api response as JSON", "Could not decode"))
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ classifiers=[
|
|||
"Operating System :: OS Independent",
|
||||
]
|
||||
requires-python= ">=3.9"
|
||||
version = "0.0.7"
|
||||
version = "0.0.8"
|
||||
|
||||
dependencies = [
|
||||
"argcomplete==2.0.0",
|
||||
|
|
Loading…
Add table
Reference in a new issue