MKP 2.4.0, added password store for app password, some changes for compatibility with NC 28

This commit is contained in:
mellis
2024-01-12 12:23:21 +01:00
parent 1fbeae93a1
commit 714990bffb
7 changed files with 137 additions and 89 deletions

View File

@@ -9,6 +9,7 @@ import os
from pprint import pprint
from requests.structures import CaseInsensitiveDict
from requests.auth import HTTPBasicAuth
import cmk.utils.password_store
def showUsage():
sys.stderr.write("""CheckMK Nextcloud Special Agent
@@ -34,7 +35,7 @@ OPTIONS:
# be aware: activating this logs very sensitive information to debug files in ~/tmp
# !!DO NOT FORGET to delete these files after debugging is done!!
DEBUG = False
DEBUG = True
nc_api_endpoint = "ocs/v2.php/apps/serverinfo/api/v1/info?format=json"
nc_api_endpoint_all_users = "ocs/v1.php/cloud/users?format=json"
@@ -54,6 +55,15 @@ long_options = [
'hostname=', 'username=', 'password=', 'port=', 'token=', 'folder=', 'no-https=', 'no-cert-check=', 'help'
]
def logDebug(line):
if DEBUG:
home_path = os.getenv("HOME")
tmp_path = f"{home_path}/tmp"
help_file = f"{tmp_path}/nextcloud_{opt_hostname}_{opt_port}_debug.txt"
with open(help_file, "a") as file:
file.write(line)
def getOptions():
global opt_hostname
global opt_username
@@ -91,12 +101,7 @@ def getOptions():
elif opt in ['-h', '--help']:
showUsage()
sys.exit(0)
if DEBUG:
home_path = os.getenv("HOME")
tmp_path = f"{home_path}/tmp"
help_file = f"{tmp_path}/nextcloud_{opt_hostname}_debug.txt"
with open(help_file, "a") as file:
file.write(f"Number of Arguments: {len(sys.argv)}, Argument List: {str(sys.argv)}\n")
logDebug(f"getOptions - Number of Arguments: {len(sys.argv)}, Argument List: {str(sys.argv)}\n")
def showOptions():
print(f"Hostname: {opt_hostname}")
@@ -107,48 +112,41 @@ def showOptions():
print(f"Token: {opt_token}")
print(f"No HTTPS: {opt_no_https}")
print(f"No TLS Check: {opt_no_cert_check}")
home_path = os.getenv("HOME")
tmp_path = f"{home_path}/tmp"
help_file = f"{tmp_path}/nextcloud_{opt_hostname}_debug.txt"
with open(help_file, "a") as file:
file.write(f"Hostname: {opt_hostname}, Port: {opt_port}, No HTTPS: {opt_no_https}, No Cert Check: {opt_no_cert_check}\n")
logDebug(f"showOptions - Hostname: {opt_hostname}, Port: {opt_port}, No HTTPS: {opt_no_https}, No Cert Check: {opt_no_cert_check}\n")
def createUrl(endpoint, hostname, protocol, port, folder):
# these parameters are needed, otherwise no information about updates regarding apps and Nextcloud itself are not reported (since version 28)
params = "skipApps=false&skipUpdate=false"
if folder == "":
url = f"{protocol}://{hostname}:{port}/{endpoint}"
else:
url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}"
if DEBUG:
home_path = os.getenv("HOME")
tmp_path = f"{home_path}/tmp"
help_file = f"{tmp_path}/nextcloud_{hostname}_debug.txt"
with open(help_file, "a") as file:
file.write(f"Data URL: {url}\n")
if endpoint == nc_api_endpoint:
url = f"{url}&{params}"
logDebug(f"createUrl - Data URL: {url}\n")
return url
def createUrlUser(user, endpoint, hostname, protocol, port, folder):
params = "format=json"
if folder == "":
url = f"{protocol}://{hostname}:{port}/{endpoint}/{user}?format=json"
url = f"{protocol}://{hostname}:{port}/{endpoint}/{user}?{params}"
else:
url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}/{user}?format=json"
if DEBUG:
home_path = os.getenv("HOME")
tmp_path = f"{home_path}/tmp"
help_file = f"{tmp_path}/nextcloud_{hostname}_debug.txt"
with open(help_file, "a") as file:
file.write(f"User URL: {url}\n")
url = f"{protocol}://{hostname}:{port}/{folder}/{endpoint}/{user}?{params}"
logDebug(f"createUrlUser - User URL: {url}\n")
return url
def getData(url, verify):
def getSession(username, secret):
session = requests.session()
session.cookies.set("SameSite", "Strict")
session.auth = (username, secret)
session.headers['Accept'] = "application/json"
return session
def getData(session, url, verify):
headers = CaseInsensitiveDict()
headers["Accept"] = "application/json"
if (opt_token == '0'):
# authenticate with username and password
pwd = opt_password
else:
# authenticate with token
pwd = opt_token
response = requests.get(url, auth=HTTPBasicAuth(opt_username, pwd), headers=headers, verify=verify)
cookies = {"nc_sameSiteCookiestrict": "true"}
response = session.get(url, headers=headers, cookies=cookies, verify=verify)
status = response.status_code
if (status == 200):
jsdata = response.text
@@ -162,17 +160,12 @@ def getData(url, verify):
sys.stderr.write(f"Request response code is {response.status_code} with URL {url}\n")
sys.exit(1)
def getDataAllUsers(url, verify):
def getDataAllUsers(session, url, verify):
headers = CaseInsensitiveDict()
headers["Accept"] = "application/json"
headers["OCS-APIRequest"] = "true"
if (opt_token == '0'):
# authenticate with username and password
pwd = opt_password
else:
# authenticate with token
pwd = opt_token
response = requests.get(url, auth=HTTPBasicAuth(opt_username, pwd), headers=headers, verify=verify)
cookies = {"nc_sameSiteCookiestrict": "true"}
response = session.get(url, headers=headers, cookies=cookies, verify=verify)
status = response.status_code
if (status == 200):
jsdata = response.text
@@ -182,18 +175,12 @@ def getDataAllUsers(url, verify):
sys.stderr.write(f"Request response code is {response.status_code} with URL {url}\n")
sys.exit(1)
def getDataUser(url, verify):
#print(url)
def getDataUser(session, url, verify):
headers = CaseInsensitiveDict()
headers["Accept"] = "application/json"
headers["OCS-APIRequest"] = "true"
if (opt_token == '0'):
# authenticate with username and password
pwd = opt_password
else:
# authenticate with token
pwd = opt_token
response = requests.get(url, auth=HTTPBasicAuth(opt_username, pwd), headers=headers, verify=verify)
cookies = {"nc_sameSiteCookiestrict": "true"}
response = session.get(url, headers=headers, cookies=cookies, verify=verify)
status = response.status_code
if (status == 200):
jsdata = response.text
@@ -208,6 +195,12 @@ def doCmkOutput(data):
print(f"NC_Version;{data['ocs']['data']['nextcloud']['system']['version']}")
print(f"NC_Freespace;{data['ocs']['data']['nextcloud']['system']['freespace']}")
print(f"NC_Status;{data['ocs']['meta']['status']}")
# This update info is available only from version 28 onwards, so the key "update" does not exist in all versions before
try:
print(f"NC_Last_Update;{data['ocs']['data']['nextcloud']['system']['update']['lastupdatedat']}")
print(f"NC_Update_Available;{data['ocs']['data']['nextcloud']['system']['update']['available']}")
except KeyError:
pass
print(f"NC_Num_Users;{data['ocs']['data']['nextcloud']['storage']['num_users']}")
print(f"NC_Num_Files;{data['ocs']['data']['nextcloud']['storage']['num_files']}")
print(f"NC_Num_Shares;{data['ocs']['data']['nextcloud']['shares']['num_shares']}")
@@ -215,8 +208,12 @@ def doCmkOutput(data):
print(f"NC_Num_Storages_Home;{data['ocs']['data']['nextcloud']['storage']['num_storages_home']}")
print(f"NC_Num_Storages_Local;{data['ocs']['data']['nextcloud']['storage']['num_storages_local']}")
print(f"NC_Num_Storages_Other;{data['ocs']['data']['nextcloud']['storage']['num_storages_other']}")
print(f"NC_Num_Apps_Installed;{data['ocs']['data']['nextcloud']['system']['apps']['num_installed']}")
print(f"NC_Num_Apps_Updates_Available;{data['ocs']['data']['nextcloud']['system']['apps']['num_updates_available']}")
# Workaround for Nextcloud 28.0.1 (KeyError "apps")
try:
print(f"NC_Num_Apps_Installed;{data['ocs']['data']['nextcloud']['system']['apps']['num_installed']}")
print(f"NC_Num_Apps_Updates_Available;{data['ocs']['data']['nextcloud']['system']['apps']['num_updates_available']}")
except KeyError:
pass
print(f"NC_Active_Users_Last_5Min;{data['ocs']['data']['activeUsers']['last5minutes']}")
print(f"NC_Active_Users_Last_1Hour;{data['ocs']['data']['activeUsers']['last1hour']}")
print(f"NC_Active_Users_Last_1Day;{data['ocs']['data']['activeUsers']['last24hours']}")
@@ -234,11 +231,11 @@ def doCmkOutput(data):
else:
print(f"NC_OPCache_Hit_Rate;0")
def doCmkOutputAllUsers(data, verify, hostname, protocol, port, folder):
def doCmkOutputAllUsers(session, data, verify, hostname, protocol, port, folder):
print("<<<nextcloud_users:sep(59)>>>")
for user in data['ocs']['data']['users']:
nc_url = createUrlUser(user, nc_api_endpoint_user, hostname, protocol, port, folder)
user_data = getDataUser(nc_url, verify)
user_data = getDataUser(session, nc_url, verify)
userid = user_data['ocs']['data']['id']
displayname = user_data['ocs']['data']['displayname']
lastlogin = int(user_data['ocs']['data']['lastLogin'])
@@ -260,6 +257,8 @@ def doCmkOutputAllUsers(data, verify, hostname, protocol, port, folder):
print(f"{userid};{displayname};{lastlogin};{quota_free};{quota_quota};{quota_relative};{quota_total};{quota_used}")
def main():
# replace password from pwd store
cmk.utils.password_store.replace_passwords()
getOptions()
if (opt_hostname == ""):
sys.stderr.write(f"No hostname given.\n")
@@ -290,13 +289,22 @@ def main():
sys.exit(1)
if (protocol == "https" and port == "80"):
sys.stderr.write(f"Combining HTTPS with port 80 is not supported.\n")
sys.exit(1)
sys.exit(1)
if (opt_token == '0'):
# authenticate with username and password
pwd = opt_password
else:
# authenticate with token
pwd = opt_token
# create session
session = getSession(opt_username, pwd)
nc_url = createUrl(nc_api_endpoint, opt_hostname, protocol, port, opt_folder)
nc_data = getData(nc_url, verify)
nc_data = getData(session, nc_url, verify)
doCmkOutput(nc_data)
nc_url = createUrl(nc_api_endpoint_all_users, opt_hostname, protocol, port, opt_folder)
nc_data = getDataAllUsers(nc_url, verify)
doCmkOutputAllUsers(nc_data, verify, opt_hostname, protocol, port, opt_folder)
nc_data = getDataAllUsers(session, nc_url, verify)
doCmkOutputAllUsers(session, nc_data, verify, opt_hostname, protocol, port, opt_folder)
if __name__ == "__main__":
main()

View File

@@ -3,7 +3,7 @@ def agent_nextcloud_arguments(params, hostname, ipaddress):
"--hostname", params["hostname"],
"--username", params["username"],
"--password", params["password"],
"--token", params["token"],
"--token", passwordstore_get_cmdline("%s", params["token"]),
"--port", params["port"],
"--folder", params["folder"],
"--no-https", params["no_https"],

View File

@@ -1,10 +1,13 @@
from cmk.gui.i18n import _
from cmk.gui.plugins.wato import (
CheckParameterRulespecWithItem,
rulespec_registry,
RulespecGroupCheckParametersOperatingSystem,
)
#!/usr/bin/env python3
from cmk.gui.i18n import _
from cmk.gui.plugins.wato.special_agents.common import RulespecGroupDatasourceProgramsApps
from cmk.gui.plugins.wato.utils import (
HostRulespec,
Rulespec,
IndividualOrStoredPassword,
rulespec_registry,
)
from cmk.gui.valuespec import (
Dictionary,
ListChoice,
@@ -13,11 +16,8 @@ from cmk.gui.valuespec import (
Password,
)
from cmk.gui.plugins.wato import (
HostRulespec,
)
from cmk.gui.plugins.wato.datasource_programs import RulespecGroupDatasourceProgramsCustom
def _factory_default_special_agent_nextcloud():
return Rulespec.FACTORY_DEFAULT_UNUSED
def _valuespec_special_agent_nextcloud():
return Dictionary(
@@ -33,7 +33,7 @@ def _valuespec_special_agent_nextcloud():
("password", Password(title=_("Password"),
allow_empty=True,
help=_("Specify password OR token, not both, token recommended"))),
("token", Password(title=_("Token"),
("token", IndividualOrStoredPassword(title=_("App Password"),
allow_empty=True,
help=_("Specify password OR token, not both, token recommended"))),
("port", TextAscii(title=_("Port"),
@@ -52,8 +52,9 @@ def _valuespec_special_agent_nextcloud():
rulespec_registry.register(
HostRulespec(
group=RulespecGroupDatasourceProgramsCustom,
name="special_agents:nextcloud",
valuespec=_valuespec_special_agent_nextcloud,
factory_default = _factory_default_special_agent_nextcloud(),
group = RulespecGroupDatasourceProgramsApps,
name = "special_agents:nextcloud",
valuespec = _valuespec_special_agent_nextcloud,
)
)