URL: https://github.com/SSSD/sssd/pull/5712 Author: justin-stephenson Title: #5712: Health and Support Analyzer - Add request log parsing utility Action: synchronized
To pull the PR as Git branch: git remote add ghsssd https://github.com/SSSD/sssd git fetch ghsssd pull/5712/head:pr5712 git checkout pr5712
From 91bd28408c917ab07bf9a0777b1902d2d11d3ecc Mon Sep 17 00:00:00 2001 From: Justin Stephenson <jstep...@redhat.com> Date: Wed, 4 Aug 2021 14:57:55 +0000 Subject: [PATCH 1/3] DP: Log offline warning for REQ_TRACE tracking This allows the sssctl analyze parsing tool to report if the backend was offline when the request came in to the data provider. --- src/providers/data_provider/dp_request.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/providers/data_provider/dp_request.c b/src/providers/data_provider/dp_request.c index 077b361f3c..3cbd55c1ec 100644 --- a/src/providers/data_provider/dp_request.c +++ b/src/providers/data_provider/dp_request.c @@ -133,6 +133,10 @@ static errno_t dp_attach_req(struct dp_req *dp_req, SSS_REQ_TRACE_CID_DP_REQ(SSSDBG_TRACE_FUNC, dp_req->name, "New request. [%s CID #%u] Flags [%#.4x].", sender_name, cli_id, dp_flags); + if (be_is_offline(provider->be_ctx)) { + DEBUG(SSSDBG_TRACE_FUNC, "[CID #%u] Backend is offline! " \ + "Using cached data if available\n", cli_id); + } } else { SSS_REQ_TRACE_CID_DP_REQ(SSSDBG_TRACE_FUNC, dp_req->name, "New request. Flags [%#.4x].", From 840f829cc0b0d616e2bdab1a5213b48351e5a34c Mon Sep 17 00:00:00 2001 From: Justin Stephenson <jstep...@redhat.com> Date: Fri, 11 Jun 2021 11:04:59 -0400 Subject: [PATCH 2/3] TOOLS: Add sss_analyze utility Add log parsing tool which can be used to track requests across responder and backend logs. --- Makefile.am | 2 +- configure.ac | 1 + contrib/sssd.spec.in | 3 + src/tools/analyzer/Makefile.am | 11 ++ src/tools/analyzer/module_request.py | 186 ++++++++++++++++++++++++++ src/tools/analyzer/source_files.py | 58 ++++++++ src/tools/analyzer/source_journald.py | 31 +++++ src/tools/analyzer/sss_analyze.py | 55 ++++++++ 8 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 src/tools/analyzer/Makefile.am create mode 100644 src/tools/analyzer/module_request.py create mode 100644 src/tools/analyzer/source_files.py create mode 100644 src/tools/analyzer/source_journald.py create mode 100755 src/tools/analyzer/sss_analyze.py diff --git a/Makefile.am b/Makefile.am index 577935e7e6..ececc91210 100644 --- a/Makefile.am +++ b/Makefile.am @@ -22,7 +22,7 @@ SUBDIRS += src/man endif SUBDIRS += . src/tests/cwrap src/tests/intg src/tests/test_CA \ - src/tests/test_ECC_CA + src/tests/test_ECC_CA src/tools/analyzer # Some old versions of automake don't define builddir builddir ?= . diff --git a/configure.ac b/configure.ac index c14a59eef1..2b1ec4a0e8 100644 --- a/configure.ac +++ b/configure.ac @@ -540,6 +540,7 @@ AC_CONFIG_FILES([Makefile contrib/sssd.spec src/examples/rwtab src/doxy.config src/lib/sifp/sss_simpleifp.doxy src/config/setup.py src/systemtap/sssd.stp + src/tools/analyzer/Makefile src/config/SSSDConfig/__init__.py]) AC_CONFIG_FILES([sbus_generate.sh], [chmod +x sbus_generate.sh]) AC_OUTPUT diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index e2877b64b1..01a18400e4 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -199,6 +199,8 @@ Requires: sssd-common = %{version}-%{release} Requires: python3-sss = %{version}-%{release} Requires: python3-sssdconfig = %{version}-%{release} Requires: libsss_certmap = %{version}-%{release} +# required by sss_analyze +Requires: python3-systemd Recommends: sssd-dbus %description tools @@ -843,6 +845,7 @@ done %{_sbindir}/sss_debuglevel %{_sbindir}/sss_seed %{_sbindir}/sssctl +%{python3_sitelib}/sssd/ %{_mandir}/man8/sss_obfuscate.8* %{_mandir}/man8/sss_override.8* %{_mandir}/man8/sss_debuglevel.8* diff --git a/src/tools/analyzer/Makefile.am b/src/tools/analyzer/Makefile.am new file mode 100644 index 0000000000..921e7d4335 --- /dev/null +++ b/src/tools/analyzer/Makefile.am @@ -0,0 +1,11 @@ +pkgpythondir = $(python3dir)/sssd + +dist_pkgpython_SCRIPTS = \ + sss_analyze.py \ + $(NULL) + +dist_pkgpython_DATA = \ + module_request.py \ + source_files.py \ + source_journald.py \ + $(NULL) diff --git a/src/tools/analyzer/module_request.py b/src/tools/analyzer/module_request.py new file mode 100644 index 0000000000..16ea81c331 --- /dev/null +++ b/src/tools/analyzer/module_request.py @@ -0,0 +1,186 @@ +import re +import copy +from enum import Enum + +import source_files + + +class Analyzer: + consumed_logs = [] + done = "" + + """ Request tracking module """ + def add_options(self, parser): + subparser = parser.add_subparsers(dest='subcommand', metavar=None, + required=True) + request_parser = subparser.add_parser('request', help='Track requests' + ' across SSSD components') + request_parser.add_argument("--list", + help="List recent client requests", + action="store_true") + request_parser.add_argument("--merge", + help="Merge logs together, sorted by" + " timestamp (requires " + "debug_microseconds = True)", + action="store_true") + request_parser.add_argument("--cid", + help="Print logs related to the " + "provided Client ID", nargs=1) + request_parser.add_argument("--pam", + help="Use with --cid to track PAM related " + "requests", action="store_true") + request_parser.add_argument("--cachereq", + help="Include cache request related logs", + action="store_true") + self.parser = request_parser + + # iterate through source, returning lines which match + # any number of patterns + def matched_line(self, source, patterns): + for line in source: + for pattern in patterns: + re_obj = re.compile(pattern) + if re_obj.search(line): + if line.startswith(' * '): + continue + yield line + + # retrieve list of associated REQ_TRACE ids + def get_linked_ids(self, source, pattern, regex): + linked_ids = [] + for match in self.matched_line(source, pattern): + id_re = re.compile(regex) + match = id_re.search(match) + if match: + found = match.group(0) + linked_ids.append(found) + return linked_ids + + def consume_line(self, line, source, consume): + found_results = True + if consume: + self.consumed_logs.append(line.rstrip(line[-1])) + else: + # files source includes newline + if type(source) == source_files.Reader: + print(line, end='') + else: + print(line) + return found_results + + def execute(self, source, options): + if options['list']: + if options['merge']: + print("--merge has no effect with --list") + self.list_requests(source, options['pam']) + elif options['cid']: + cid = options['cid'][0] + if not cid: + print("sss_analyze request: error: argument" + " --cid: expected 1 argument") + else: + self.track_request(source, options) + else: + self.parser.print_help() + + def print_formatted(self, line): + # exclude backtrace logs + if line.startswith(' * '): + return + # files source includes newline + fields = line.split("[") + cr_field = fields[2].split(":")[1] + cr = cr_field[5:] + if "refreshed" in line: + return + # CR Plugin name + if re.search("cache_req_send", line): + plugin = line.split('\'')[1] + print(" - " + plugin) + # CR Input name + elif re.search("cache_req_process_input", line): + name = line.rsplit('[')[-1] + if cr not in self.done: + print(" - " + name[:-2]) + self.done = cr + # CR Input id + elif re.search("cache_req_search_send", line): + id = line.rsplit()[-1] + if cr not in self.done: + print(" - " + id) + self.done = cr + # CID and client process name + else: + ts = line.split(")")[0] + fields = line.split("[") + cid = fields[3][5:-1] + cmd = fields[4][4:-1] + print(ts + ": " + "CID #" + cid + ": " + cmd) + + def list_requests(self, source, pam): + component = source.Component.NSS + resp = "nss" + patterns = ['\[cmd'] + patterns.append("(cache_req_send|cache_req_process_input|" + "cache_req_search_send)") + consume = True + if pam: + component = source.Component.PAM + resp = "pam" + + print(f"******** Listing {resp} client requests ********") + source.set_component(component) + self.done = "" + # For each CID + for match in self.matched_line(source, patterns): + self.print_formatted(match) + + def track_request(self, source, options): + merge = options['merge'] + client_id = options['cid'][0] + resp_results = False + be_results = False + component = source.Component.NSS + resp = "nss" + pattern = [f'REQ_TRACE.*\[CID #{client_id}\\]'] + pattern.append(f"\[CID #{client_id}\\].*connected") + + if options['pam']: + component = source.Component.PAM + resp = "pam" + pam_data_regex = f'pam_print_data.*\[CID #{client_id}\]' + + print(f"******** Checking {resp} responder for Client ID" + f" {client_id} *******") + source.set_component(component) + if options['cachereq']: + cr_id_regex = 'CR #[0-9]+' + cr_ids = self.get_linked_ids(source, pattern, cr_id_regex) + [pattern.append(f'{id}\:') for id in cr_ids] + + for match in self.matched_line(source, pattern): + resp_results = self.consume_line(match, source, merge) + + print(f"********* Checking Backend for Client ID {client_id} ********") + pattern = [f'REQ_TRACE.*\[sssd.{resp} CID #{client_id}\]'] + source.set_component(source.Component.BE) + + be_id_regex = '\[RID#[0-9]+\]' + be_ids = self.get_linked_ids(source, pattern, be_id_regex) + + pattern.clear() + [pattern.append(f'\\{id}') for id in be_ids] + + if options['pam']: + pattern.append(pam_data_regex) + for match in self.matched_line(source, pattern): + be_results = self.consume_line(match, source, merge) + + if merge: + # sort by date/timestamp + sorted_list = sorted(self.consumed_logs, + key=lambda s: s.split(')')[0]) + for entry in sorted_list: + print(entry) + if not resp_results and not be_results: + print(f"ID {client_id} not found in logs!") diff --git a/src/tools/analyzer/source_files.py b/src/tools/analyzer/source_files.py new file mode 100644 index 0000000000..12be10f895 --- /dev/null +++ b/src/tools/analyzer/source_files.py @@ -0,0 +1,58 @@ +from enum import Enum +import configparser +from os import listdir +from os.path import isfile, join +import glob + + +class Reader: + class Component(Enum): + NSS = 1 # NSS Responder + PAM = 2 # PAM Responder + BE = 3 # Backend + + def __init__(self, path): + self.log_files = [] + self.path = self.resolve_path(path) + self.domains = self.get_domain_logfiles() + + def __iter__(self): + for files in self.log_files: + try: + with open(files) as file: + for line in file: + yield line + except FileNotFoundError as err: + print("Could not find domain log file, skipping") + print(err) + continue + + def resolve_path(self, path): + if path.endswith("/"): + return path + else: + return path + "/" + + def get_domain_logfiles(self): + domain_files = [] + exclude_list = ["ifp", "nss", "pam", "sudo", "autofs", + "ssh", "pac", "kcm", ".gz"] + file_list = glob.glob(self.path + "sssd_*") + for file in file_list: + if not any(s in file for s in exclude_list): + domain_files.append(file) + + return domain_files + + def set_component(self, component): + self.log_files = [] + if component == self.Component.NSS: + self.log_files.append(self.path + "sssd_nss.log") + elif component == self.Component.PAM: + self.log_files.append(self.path + "sssd_pam.log") + elif component == self.Component.BE: + if not self.domains: + raise IOError + # error: No domains found? + for dom in self.domains: + self.log_files.append(dom) diff --git a/src/tools/analyzer/source_journald.py b/src/tools/analyzer/source_journald.py new file mode 100644 index 0000000000..8011209d4a --- /dev/null +++ b/src/tools/analyzer/source_journald.py @@ -0,0 +1,31 @@ +from systemd import journal + +from enum import Enum + +_EXE_PREFIX = "/usr/libexec/sssd/" +_NSS_MATCH = _EXE_PREFIX + "sssd_nss" +_PAM_MATCH = _EXE_PREFIX + "sssd_pam" +_BE_MATCH = _EXE_PREFIX + "sssd_be" + + +class Reader: + class Component(Enum): + NSS = 1 # NSS Responder + PAM = 2 # PAM Responder + BE = 3 # Backend + + def __init__(self): + self.reader = journal.Reader() + self.reader.this_boot() + + def __iter__(self): + for entry in self.reader: + yield entry['MESSAGE'] + + def set_component(self, component): + if component == self.Component.NSS: + self.reader.add_match(_EXE=_NSS_MATCH) + elif component == self.Component.PAM: + self.reader.add_match(_EXE=_PAM_MATCH) + elif component == self.Component.BE: + self.reader.add_match(_EXE=_BE_MATCH) diff --git a/src/tools/analyzer/sss_analyze.py b/src/tools/analyzer/sss_analyze.py new file mode 100755 index 0000000000..bf338abb4f --- /dev/null +++ b/src/tools/analyzer/sss_analyze.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 + +import sys +import argparse +import re + +import source_files +import module_request + + +def add_options(): + parser = argparse.ArgumentParser('sss_analyze') + parser.add_argument( + '--source', action='store', type=str, default='files', + choices=['files', 'journald'] + ) + parser.add_argument( + '--logdir', action='store', type=str, default='/var/log/sssd/' + ) + return parser + + +def load_module(module, parser): + if module == 'request': + analyzer = module_request.Analyzer() + else: + return + analyzer.add_options(parser) + return analyzer + + +def main(): + parser = add_options() + + module = load_module('request', parser) + + if not len(sys.argv) > 1: + parser.print_help() + exit(0) + + ns = parser.parse_args() + args = vars(ns) + path = args['logdir'] + + if args['source'] == "journald": + import source_journald + reader = source_journald.Reader() + else: + reader = source_files.Reader(path) + + module.execute(reader, args) + + +if __name__ == '__main__': + main() From a5a7b8e48de36094cc04d57ccc244ec63f2f7b5b Mon Sep 17 00:00:00 2001 From: Justin Stephenson <jstep...@redhat.com> Date: Fri, 21 May 2021 14:42:41 -0400 Subject: [PATCH 3/3] SSSCTL: Add analyze command Wrapper for sss_analyze Print a message about limited functionality when tevent chain ID support is not built. --- Makefile.am | 1 + src/tools/sssctl/sssctl.c | 1 + src/tools/sssctl/sssctl.h | 4 ++++ src/tools/sssctl/sssctl_logs.c | 29 +++++++++++++++++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/Makefile.am b/Makefile.am index ececc91210..2ff86c8b41 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2062,6 +2062,7 @@ sssctl_LDADD = \ $(NULL) sssctl_CFLAGS = \ $(AM_CFLAGS) \ + -DPYTHONDIR_PATH=\"$(python3dir)/sssd\" \ $(NULL) if BUILD_SUDO diff --git a/src/tools/sssctl/sssctl.c b/src/tools/sssctl/sssctl.c index 8adaf30910..3816125ad3 100644 --- a/src/tools/sssctl/sssctl.c +++ b/src/tools/sssctl/sssctl.c @@ -296,6 +296,7 @@ int main(int argc, const char **argv) SSS_TOOL_COMMAND("logs-remove", "Remove existing SSSD log files", 0, sssctl_logs_remove), SSS_TOOL_COMMAND("logs-fetch", "Archive SSSD log files in tarball", 0, sssctl_logs_fetch), SSS_TOOL_COMMAND("debug-level", "Change SSSD debug level", 0, sssctl_debug_level), + SSS_TOOL_COMMAND("analyze", "Analyze logged data", 0, sssctl_analyze), #ifdef HAVE_LIBINI_CONFIG_V1_3 SSS_TOOL_DELIMITER("Configuration files tools:"), SSS_TOOL_COMMAND_FLAGS("config-check", "Perform static analysis of SSSD configuration", 0, sssctl_config_check, SSS_TOOL_FLAG_SKIP_CMD_INIT), diff --git a/src/tools/sssctl/sssctl.h b/src/tools/sssctl/sssctl.h index 599ef65196..c827bb8789 100644 --- a/src/tools/sssctl/sssctl.h +++ b/src/tools/sssctl/sssctl.h @@ -96,6 +96,10 @@ errno_t sssctl_debug_level(struct sss_cmdline *cmdline, struct sss_tool_ctx *tool_ctx, void *pvt); +errno_t sssctl_analyze(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt); + errno_t sssctl_user_show(struct sss_cmdline *cmdline, struct sss_tool_ctx *tool_ctx, void *pvt); diff --git a/src/tools/sssctl/sssctl_logs.c b/src/tools/sssctl/sssctl_logs.c index ebb2c4571c..b98cd68fec 100644 --- a/src/tools/sssctl/sssctl_logs.c +++ b/src/tools/sssctl/sssctl_logs.c @@ -41,6 +41,7 @@ #define LOG_FILE(file) " " LOG_PATH "/" file #define LOG_FILES LOG_FILE("*.log") +#define SSS_ANALYZE PYTHONDIR_PATH"/sss_analyze.py" #define CHECK(expr, done, msg) do { \ if (expr) { \ @@ -388,3 +389,31 @@ errno_t sssctl_debug_level(struct sss_cmdline *cmdline, talloc_free(ctx); return ret; } + +errno_t sssctl_analyze(struct sss_cmdline *cmdline, + struct sss_tool_ctx *tool_ctx, + void *pvt) +{ + errno_t ret; + +#ifndef BUILD_CHAIN_ID + PRINT("NOTE: Tevent chain ID support missing, request analysis will be limited.\n"); + PRINT("It is recommended to use the --logdir option against tevent chain ID " + "supported SSSD logs.\n"); +#endif + const char **args = talloc_array_size(tool_ctx, + sizeof(char *), + cmdline->argc + 2); + if (!args) { + return ENOMEM; + } + memcpy(&args[1], cmdline->argv, sizeof(char *) * cmdline->argc); + args[0] = SSS_ANALYZE; + args[cmdline->argc + 1] = NULL; + + ret = sssctl_run_command(args); + + talloc_free(args); + + return ret; +}
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org Do not reply to spam on the list, report it: https://pagure.io/fedora-infrastructure