Merge "Audit plugin for SNMP operations"
This commit is contained in:
commit
00e7c21d32
@ -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
|
||||
|
30
stx-snmp-helm/centos/docker/stx-snmp/src/Makefile
Normal file
30
stx-snmp-helm/centos/docker/stx-snmp/src/Makefile
Normal file
@ -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)
|
427
stx-snmp-helm/centos/docker/stx-snmp/src/snmpAuditPlugin.c
Normal file
427
stx-snmp-helm/centos/docker/stx-snmp/src/snmpAuditPlugin.c
Normal file
@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Wind River Systems, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <net-snmp/net-snmp-config.h>
|
||||
#include <net-snmp/net-snmp-includes.h>
|
||||
#include <net-snmp/net-snmp-features.h>
|
||||
#include <net-snmp/agent/net-snmp-agent-includes.h>
|
||||
#include <net-snmp/library/callback.h>
|
||||
#include <net-snmp/library/snmp_transport.h>
|
||||
#include <net-snmp/library/snmp_api.h>
|
||||
#include <net-snmp/library/mib.h>
|
||||
#include <net-snmp/library/snmp.h>
|
||||
#include <net-snmp/library/vacm.h>
|
||||
#include <net-snmp/library/snmpUDPDomain.h>
|
||||
#include <net-snmp/library/tools.h>
|
||||
#include <net-snmp/agent/agent_callbacks.h>
|
||||
#include <net-snmp/agent/agent_handler.h>
|
||||
#include <net-snmp/agent/agent_registry.h>
|
||||
|
||||
#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);
|
||||
}
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user