diff --git a/packages/ns-api/Makefile b/packages/ns-api/Makefile index 15868d3ab..9679d7d97 100644 --- a/packages/ns-api/Makefile +++ b/packages/ns-api/Makefile @@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=ns-api -PKG_VERSION:=0.0.9 +PKG_VERSION:=0.0.10 PKG_RELEASE:=1 PKG_BUILD_DIR:=$(BUILD_DIR)/ns-api-$(PKG_VERSION) @@ -72,6 +72,8 @@ define Package/ns-api/install $(INSTALL_BIN) ./files/ns.firewall $(1)/usr/libexec/rpcd/ $(INSTALL_DATA) ./files/ns.firewall.json $(1)/usr/share/rpcd/acl.d/ $(INSTALL_BIN) ./files/ns.flashstart $(1)/usr/libexec/rpcd/ + $(INSTALL_DATA) ./files/ns.log.json $(1)/usr/share/rpcd/acl.d/ + $(INSTALL_BIN) ./files/ns.log $(1)/usr/libexec/rpcd/ $(INSTALL_DATA) ./files/ns.flashstart.json $(1)/usr/share/rpcd/acl.d/ $(INSTALL_BIN) ./files/ns.dpireport $(1)/usr/libexec/rpcd/ $(INSTALL_DATA) ./files/ns.dpireport.json $(1)/usr/share/rpcd/acl.d/ diff --git a/packages/ns-api/README.md b/packages/ns-api/README.md index f93d7b071..b4d026a4a 100644 --- a/packages/ns-api/README.md +++ b/packages/ns-api/README.md @@ -2352,3 +2352,34 @@ If the storage is configured, the response will be like: "vendor": "QEMU" } ``` + +## ns.log + +Show and filter logs. + +### get-log + +```bash +api-cli ns.log get-log --data '{"limit": 10, "search: "mwan"}' +``` + +Parameter list: + +- `limit`: number of lines to show +- `search`: search string, uses `grep` syntax + +Both parameters are _optional_ + +Example response: + +```json +{ + "values": [ + "Oct 12 08:56:55 NethSec dropbear[21682]: Exit (root) from : Disconnect received", + "Oct 12 09:00:00 NethSec crond[4002]: USER root pid 22583 cmd sleep $(( RANDOM % 60 )); /usr/sbin/send-heartbeat", + "..." + ] +} +``` + +**Notes**: returning strings are syslog formatted, be aware of it if any parsing is needed. diff --git a/packages/ns-api/files/ns.log b/packages/ns-api/files/ns.log new file mode 100755 index 000000000..30ce75691 --- /dev/null +++ b/packages/ns-api/files/ns.log @@ -0,0 +1,53 @@ +#!/usr/bin/python3 + +# +# Copyright (C) 2023 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-2.0-only +# + +import json +import subprocess +import sys +from json import JSONDecodeError + +from nethsec import utils + +cmd = sys.argv[1] + +if cmd == 'list': + print(json.dumps({ + 'get-log': { + 'search': 'String', + 'limit': 32 + } + })) +elif cmd == 'call': + action = sys.argv[2] + if action == 'get-log': + try: + data = json.JSONDecoder().decode(sys.stdin.read()) + # parse params + limit = data['limit'] if 'limit' in data else 100 + search = data['search'] if 'search' in data else None + + # search in log + if search is not None: + result = subprocess.run(['grep', search, '/var/log/messages'], capture_output=True, check=True).stdout + else: + result = subprocess.run(['cat', '/var/log/messages'], capture_output=True, check=True).stdout + + result = subprocess.run(['tail', '-n', str(limit)], input=result, capture_output=True, check=True).stdout + + print(json.dumps({ + 'values': result.decode('utf-8').split('\n')[:-1] + })) + except JSONDecodeError as ex: + print(json.dumps(utils.generic_error('Invalid JSON'))) + except subprocess.CalledProcessError as ex: + decoded_error: str = ex.stderr.decode('utf-8').strip('\n') + if ex.cmd[0] == 'grep': + print(json.dumps(utils.validation_error('search', decoded_error.removesuffix('grep: ')))) + elif ex.cmd[0] == 'tail': + print(json.dumps(utils.validation_error('limit', decoded_error.removesuffix('tail: ')))) + else: + print(json.dumps(utils.generic_error(decoded_error))) diff --git a/packages/ns-api/files/ns.log.json b/packages/ns-api/files/ns.log.json new file mode 100644 index 000000000..1c159f568 --- /dev/null +++ b/packages/ns-api/files/ns.log.json @@ -0,0 +1,13 @@ +{ + "log-manager": { + "description": "View and search system logs", + "write": {}, + "read": { + "ubus": { + "ns.log": [ + "*" + ] + } + } + } +}