diff --git a/stx-snmp-helm/centos/docker/stx-snmp/Dockerfile b/stx-snmp-helm/centos/docker/stx-snmp/Dockerfile index cb9328b..549eb24 100755 --- a/stx-snmp-helm/centos/docker/stx-snmp/Dockerfile +++ b/stx-snmp-helm/centos/docker/stx-snmp/Dockerfile @@ -1,11 +1,28 @@ +FROM centos:8 AS builder +RUN yum -y update +RUN yum -y upgrade +RUN yum -y install \ + net-snmp-libs \ + net-snmp-devel \ + libuuid\ + gcc\ + make +RUN ln -s /usr/lib64/libuuid.so.1.3.0 /usr/lib64/libuuid.so +WORKDIR /home +COPY ./src/* /home/src/ +WORKDIR /home/src/ +RUN make -f Makefile + FROM centos:8 RUN yum -y update +RUN yum -y upgrade RUN yum -y install\ net-snmp \ net-snmp-utils \ net-snmp-libs COPY ./mibs/* /usr/share/snmp/mibs/ +COPY --from=builder /home/src/libsnmpAuditPlugin.so /usr/lib64/ EXPOSE 161/udp EXPOSE 162/udp EXPOSE 705/tcp diff --git a/stx-snmp-helm/centos/docker/stx-snmp/src/Makefile b/stx-snmp-helm/centos/docker/stx-snmp/src/Makefile new file mode 100644 index 0000000..7f759e1 --- /dev/null +++ b/stx-snmp-helm/centos/docker/stx-snmp/src/Makefile @@ -0,0 +1,30 @@ +SRCS = snmpAuditPlugin.c + +OBJS = $(SRCS:.c=.o) +LDLIBS = -lnetsnmp -lnetsnmpagent -luuid +INCLUDES += -I./ +CCFLAGS = -fPIC -g -O2 -Wall -Werror -Wformat -Wformat-security +LIBCGTSAGENT_SO := libsnmpAuditPlugin.so +MAJOR ?= 1 +MINOR ?= 0 +PATCH ?= 0 +DESTDIR ?= /usr/lib64 + +build: lib + +.c.o: + $(CC) $(CCFLAGS) $(INCLUDES) -c $< -o $@ + +lib: $(OBJS) + $(CC) $(OBJS) $(LDLIBS) -o $(LIBCGTSAGENT_SO).$(MAJOR).$(MINOR).$(PATCH) -shared + ln -sf $(LIBCGTSAGENT_SO).$(MAJOR).$(MINOR).$(PATCH) $(LIBCGTSAGENT_SO).$(MAJOR) + ln -sf $(LIBCGTSAGENT_SO).$(MAJOR).$(MINOR).$(PATCH) $(LIBCGTSAGENT_SO) + +clean: + @rm -f $(OBJ) *.o *.so.* + +install: + install -m 755 -d $(DESTDIR)$(LIB_DIR) + install -m 755 $(LIBCGTSAGENT_SO).$(MAJOR).$(MINOR).$(PATCH) $(DESTDIR)$(LIB_DIR) + ln -s $(LIBCGTSAGENT_SO).$(MAJOR).$(MINOR).$(PATCH) $(DESTDIR)$(LIB_DIR)/$(LIBCGTSAGENT_SO).$(MAJOR) + ln -s $(LIBCGTSAGENT_SO).$(MAJOR).$(MINOR).$(PATCH) $(DESTDIR)$(LIB_DIR)/$(LIBCGTSAGENT_SO) diff --git a/stx-snmp-helm/centos/docker/stx-snmp/src/snmpAuditPlugin.c b/stx-snmp-helm/centos/docker/stx-snmp/src/snmpAuditPlugin.c new file mode 100644 index 0000000..da755f1 --- /dev/null +++ b/stx-snmp-helm/centos/docker/stx-snmp/src/snmpAuditPlugin.c @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2020 Wind River Systems, Inc. +* +* SPDX-License-Identifier: Apache-2.0 +* + */ + + + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_BUF_STR (256) + +#define AUDIT_TAG "snmp-auditor" + +/* Used to keep track of the first handler call for a transaction */ +typedef struct s_audit_req_t { + long transid; + long reqid; +} audit_req_t; + +typedef struct s_enum_to_string_t { + const int enumval; + const char *str; +} enum_to_string_t; + + +/* Logs IP session information, in the format: "remote IP:port ==> local IP:port" */ +static inline char* fmtaddr(const char *prefix, int af, + void *remote_addr, unsigned short remote_port, + char*buf, size_t buflen) +{ + char remote_addr_str[LOG_BUF_STR+1]; + + if (NULL == inet_ntop(af, remote_addr, remote_addr_str, sizeof(remote_addr_str))) { + strncpy(remote_addr_str, "UNKNOWN", LOG_BUF_STR+1); + } + remote_addr_str[LOG_BUF_STR] = 0; + + int ret = snprintf(buf, buflen, "transport:%s remote:%s", prefix, remote_addr_str); + if (ret < 0) { + snmp_log(LOG_ERR, AUDIT_TAG" format address error: snprintf buffer " \ + "error with size %d and values %s %s\n", ret, prefix, remote_addr_str); + abort(); + } + return buf; +} + + +#ifdef NETSNMP_ENABLE_IPV6 +static char* ipv6_fmtaddr(const char *prefix, netsnmp_addr_pair *addr_pair, char*buf, size_t buflen) +{ + return fmtaddr(prefix, AF_INET6, + (void *)&addr_pair->remote_addr.sin6.sin6_addr, addr_pair->remote_addr.sin6.sin6_port, + buf, buflen); +} +#endif + + +static char* ipv4_fmtaddr(const char *prefix, netsnmp_addr_pair *addr_pair, char*buf, size_t buflen) +{ + return fmtaddr(prefix, AF_INET, + (void *)&addr_pair->remote_addr.sin.sin_addr, addr_pair->remote_addr.sin.sin_port, + buf, buflen); +} + + +/* Logs IP session information */ +static char* log_session_addresses(const oid* tDomain, netsnmp_addr_pair *addr_pair, char*buf, size_t buflen) +{ + if (tDomain == netsnmpUDPDomain) { + return ipv4_fmtaddr("udp", addr_pair, buf, buflen); + } + + if (tDomain == netsnmp_snmpTCPDomain) { + return ipv4_fmtaddr("tcp", addr_pair, buf, buflen); + } + +#ifdef NETSNMP_ENABLE_IPV6 + if (tDomain == netsnmp_UDPIPv6Domain) { + return ipv6_fmtaddr("udpv6", addr_pair, buf, buflen); + } + + if (tDomain == netsnmp_TCPIPv6Domain) { + return ipv6_fmtaddr("tcpv6", addr_pair, buf, buflen); + } +#endif + strncpy(buf, "IP FMT ERROR", buflen); + buf[buflen-1] = 0; + return buf; +} + + +/* SNMP OID formatting (a wrapper around the 'standard' function */ +static inline char* fmtoid(const oid * theoid, size_t len, int* no_overflow) +{ + u_char *buf = NULL; + size_t buf_len = 0; + size_t out_len = 0; + + *no_overflow = sprint_realloc_objid(&buf, &buf_len, &out_len, 1, theoid, len); + if (NULL == buf) { + *no_overflow = 0; + } + + return (char*)buf; +} + + +/* SNMP var bind formatting (a convenience function) - formats the OID (variable name) + This function is always called with var != NULL */ +static inline char* fmtmsg_var(netsnmp_variable_list * var, int* no_overflow) +{ + return fmtoid(var->name, var->name_length, no_overflow); +} + +static const char* get_version(long version) +{ + switch (version) { + case 0: return "v1"; + case 1: return "v2c"; + case 3: return "v3"; + } + return "error"; +} + + +static const char *get_str_from_enum(int enumval, const enum_to_string_t* table, const char* defval) +{ + const enum_to_string_t* ptr = table; + + for ( ; ptr->str != NULL; ++ptr) { + if (ptr->enumval == enumval) { + return ptr->str; + } + } + if (NULL == defval) { + return "unknown"; + } + return defval; +} + + +static const char *get_auth_error(int errorcode) +{ + static enum_to_string_t errorcodes_str[] = { + { VACM_SUCCESS, "Success", }, + { VACM_NOSECNAME, "InvalidCommunityName" }, + { VACM_NOGROUP, "NoGroup" }, + { VACM_NOACCESS, "NoAccess" }, + { VACM_NOVIEW, "NoViewAccess" }, + { VACM_NOTINVIEW, "NotInView" }, + { VACM_NOSUCHCONTEXT, "NoSuchContext" }, + { VACM_SUBTREE_UNKNOWN,"SubtreeUnknown" }, + {0, NULL} + }; + return get_str_from_enum(errorcode, errorcodes_str, "unknown err"); +} + +static const char *get_result_error(int errorcode) +{ + static enum_to_string_t errorcodes_str[] = { + { 0, "pass" }, + { SNMP_NOSUCHOBJECT, "NoSuchObject" }, + { SNMP_NOSUCHINSTANCE, "NoSuchInstance" }, + { SNMP_ENDOFMIBVIEW, "EndOfMIBView" }, + {0, NULL} + }; + return get_str_from_enum(errorcode, errorcodes_str, "pass"); +} + + +/* Logs all var-binds in PDU (only variable names, aka OID's) */ +static void log_var_list(netsnmp_pdu *pdu) +{ + netsnmp_variable_list * var; + + for (var = pdu->variables; var != NULL; var = var->next_variable) { + int no_overflow_var = 0; + char* var_str = fmtmsg_var(var, &no_overflow_var); + + snmp_log(LOG_INFO, AUDIT_TAG" reqid:%ld oid:%s%s\n", + pdu->reqid, + (var_str != NULL) ? var_str : "INVALID", + (no_overflow_var) ? "" : " [TRUNCATED]"); + free(var_str); + } +} + +/* Logs the 'header' of a PDU/request (IP addresses, reqid, msg type, version) */ +static void log_pdu_header(netsnmp_pdu *pdu, const char *status) +{ + char buf[LOG_BUF_STR]; + netsnmp_addr_pair *addr_pair = (netsnmp_addr_pair *)pdu->transport_data; + + snmp_log(LOG_INFO, AUDIT_TAG" %s reqid:%ld msg-type:%s version:%s%s\n", + log_session_addresses(pdu->tDomain, addr_pair, buf, sizeof(buf)), + pdu->reqid, snmp_pdu_type(pdu->command), get_version(pdu->version), status); +} + +/* Logs the results of a request, namely results obtained from actual processing handlers */ +static void log_results(long reqid, netsnmp_request_info *requests) +{ + netsnmp_request_info *req; + + for (req = requests; req != NULL; req = req->next) { + netsnmp_variable_list *var = req->requestvb; + + if (NULL == var) { + continue; + } + if (var->type != ASN_NULL) { /* NULL means no result, so skip */ + int no_overflow_var = 0; + char* var_str = fmtmsg_var(var, &no_overflow_var); + + /* Print only first variable: this is the request that we get a result for */ + snmp_log(LOG_INFO, AUDIT_TAG" reqid:%ld oid:%s%s status:%s\n", reqid, + (var_str != NULL) ? var_str : "INVALID", + (no_overflow_var) ? "" : " [TRUNCATED]", get_result_error(var->type)); + free(var_str); + } + } +} + + +static void log_invalid_oid_trees(netsnmp_pdu *pdu) +{ + char buf[LOG_BUF_STR]; + netsnmp_variable_list *var; + netsnmp_addr_pair *addr_pair = (netsnmp_addr_pair *)pdu->transport_data; + int first_time = 1; + + for (var = pdu->variables; var != NULL; var = var->next_variable) { + netsnmp_subtree *tp = netsnmp_subtree_find(var->name, var->name_length, + NULL, pdu->contextName); + if (tp != NULL) { + int prefix_len = netsnmp_oid_find_prefix(tp->start_a, + tp->start_len, + tp->end_a, tp->end_len); + while (prefix_len < 1) { + tp = tp->next; + if (NULL == tp) { + break; + } + prefix_len = netsnmp_oid_find_prefix(tp->start_a, + tp->start_len, + tp->end_a, tp->end_len); + } + DEBUGMSGTL(("helper:snmpAudit", "var=%p tp=%p prefix_len=%d\n", var, tp, prefix_len )); + } + else { + DEBUGMSGTL(("helper:snmpAudit", "tp NOT found var=%p\n", var)); + } + if (NULL == tp) { + int no_overflow_var = 0; + char* var_str = fmtmsg_var(var, &no_overflow_var); + + if (first_time) { + first_time = 0; + snmp_log(LOG_INFO, AUDIT_TAG" %s reqid:%ld msg-type:%s version:%s\n", + log_session_addresses(pdu->tDomain, addr_pair, buf, sizeof(buf)), pdu->reqid, + snmp_pdu_type(pdu->command), + get_version(pdu->version)); + log_var_list(pdu); + } + + snmp_log(LOG_INFO, AUDIT_TAG" reqid:%ld oid:%s%s status:%s\n", + pdu->reqid, + (var_str != NULL) ? var_str : "INVALID", + (no_overflow_var) ? "" : " [TRUNCATED]", + get_result_error(SNMP_ENDOFMIBVIEW)); + free(var_str); + } + } +} + +/* + * Register with 'SNMPD_CALLBACK_ACM_CHECK_INITIAL == minorId' + * This function is used to log authorization errors and invalid OID's errors, + * for GET BULK and GET NEXT requests + */ +static int audit_callback_acm_check_initial(int majorID, int minorID, void *serverarg, + void *clientarg) +{ + struct view_parameters *view_parms = + (struct view_parameters *) serverarg; + netsnmp_pdu *pdu = view_parms->pdu; + + DEBUGMSGTL(("helper:snmpAudit", "%s msg-type: %s errcode=%d minorID=%d\n", + __FUNCTION__, snmp_pdu_type(pdu->command), view_parms->errorcode, minorID)); + + if (view_parms->errorcode != VACM_SUCCESS) { + /* Log Authentication errors */ + char buf[LOG_BUF_STR]; + netsnmp_addr_pair *addr_pair = (netsnmp_addr_pair *)pdu->transport_data; + + snmp_log(LOG_INFO, AUDIT_TAG" %s reqid:%ld msg-type:%s version:%s status:%s\n", + log_session_addresses(pdu->tDomain, addr_pair, buf, sizeof(buf)), pdu->reqid, + snmp_pdu_type(pdu->command), get_version(pdu->version), + get_auth_error(view_parms->errorcode)); + log_var_list(pdu); + return 0; + } + + if (SNMP_MSG_GETBULK == pdu->command || + SNMP_MSG_GETNEXT == pdu->command) { + /* Log possible invalid OID subtrees for GETNEXT and GETBULK request + * (e.g. "1.10" - outside the normal ISO MIB subtree) + */ + log_invalid_oid_trees(pdu); + } + return 0; +} + +/* + * Register with 'SNMPD_CALLBACK_ACM_CHECK == minorId' + * This function is used to log SET requests (which are normally rejected) + */ +static int audit_callback_acm_check(int majorID, int minorID, void *serverarg, + void *clientarg) +{ + struct view_parameters *view_parms = + (struct view_parameters *) serverarg; + netsnmp_pdu *pdu = view_parms->pdu; + + DEBUGMSGTL(("helper:snmpAudit", "%s msg-type: %s errcode=%d minorID=%d\n", + __FUNCTION__, snmp_pdu_type(pdu->command), view_parms->errorcode, minorID)); + if (SNMP_MSG_SET == pdu->command) { + char status_buf[LOG_BUF_STR]; + snprintf(status_buf, LOG_BUF_STR, + " status:%s", get_auth_error(view_parms->errorcode)); + log_pdu_header(pdu, status_buf); + log_var_list(pdu); + } + return 0; +} + + +/* Main log handler function: logs 'normal' requests: + * everything except SET operations, authentication errors and GETBULK/GETNEXT for invalid OIDs */ +static int audit_log_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + static audit_req_t req = { + .transid = 0, + .reqid = 0 + }; + netsnmp_pdu *orig_pdu = reqinfo->asp->orig_pdu; + int ret; + + /* Note. Assumes single-threaded processing. */ + if ((req.transid != orig_pdu->transid) && + (req.reqid != orig_pdu->reqid)) { + + /* New transaction */ + req.transid = orig_pdu->transid; + req.reqid = orig_pdu->reqid; + + /* Logs session information (e.g. IP addresses, version...) */ + log_pdu_header(orig_pdu, ""); + /* Logs the variables names in the request */ + log_var_list(orig_pdu); + } + /* Calls the next handlers, to obtain processing results */ + ret = netsnmp_call_next_handler(handler, reginfo, reqinfo, requests); + /* Logs the variables names in the results + * resulted from the calls to 'netsnmp_call_next_handler' above + * which invokes all other handlers in the chain. + */ + log_results(orig_pdu->reqid, requests); + return ret; +} + + + +/* + * Initialization routine, automatically called by the agent + * (to get called, the function name must match init_FILENAME()) + */ +void init_snmpAuditPlugin(void) { + + netsnmp_mib_handler *audit_handler = NULL; + + snmp_log(LOG_INFO, "init_snmpAuditPlugin\n"); + audit_handler = netsnmp_create_handler("snmpAudit", + audit_log_handler); + if (audit_handler != NULL) { + netsnmp_register_handler_by_name("snmpAudit", audit_handler); + } + + netsnmp_register_callback(SNMP_CALLBACK_APPLICATION, SNMPD_CALLBACK_ACM_CHECK, + audit_callback_acm_check, + NULL, NETSNMP_CALLBACK_LOWEST_PRIORITY ); + netsnmp_register_callback(SNMP_CALLBACK_APPLICATION, SNMPD_CALLBACK_ACM_CHECK_INITIAL, + audit_callback_acm_check_initial, + NULL, NETSNMP_CALLBACK_LOWEST_PRIORITY); +} + +void deinit_snmpAuditPlugin(void) +{ + snmp_log(LOG_INFO, "deinit_snmpAuditPlugin\n"); + snmp_unregister_callback(SNMP_CALLBACK_APPLICATION, SNMPD_CALLBACK_ACM_CHECK, + audit_callback_acm_check, NULL, 1); + snmp_unregister_callback(SNMP_CALLBACK_APPLICATION, SNMPD_CALLBACK_ACM_CHECK_INITIAL, + audit_callback_acm_check_initial, NULL, 1); +} \ No newline at end of file diff --git a/stx-snmp-helm/stx-snmp-helm/helm-charts/snmp/snmpd/snmpd.conf b/stx-snmp-helm/stx-snmp-helm/helm-charts/snmp/snmpd/snmpd.conf index e172f50..309859b 100755 --- a/stx-snmp-helm/stx-snmp-helm/helm-charts/snmp/snmpd/snmpd.conf +++ b/stx-snmp-helm/stx-snmp-helm/helm-charts/snmp/snmpd/snmpd.conf @@ -9,3 +9,10 @@ mibs +ALL master agentx agentXSocket tcp:705 + + +dlmod snmpAuditPlugin /usr/lib64/libsnmpAuditPlugin.so + +# Insert the snmpAudit hander into specific sections of the mib tree +injectHandler snmpAudit null +injectHandler snmpAudit bulk_to_next \ No newline at end of file