diff --git a/docs/sources/Cisco/index.md b/docs/sources/Cisco/index.md index 9d6cad4..d2c67fd 100644 --- a/docs/sources/Cisco/index.md +++ b/docs/sources/Cisco/index.md @@ -1,5 +1,54 @@ # Vendor - Cisco +## Product - ACS + +| Ref | Link | +|----------------|---------------------------------------------------------------------------------------------------------| +| Splunk Add-on | https://splunkbase.splunk.com/app/1811/ | +| Product Manual | https://community.cisco.com/t5/security-documents/acs-5-x-configuring-the-external-syslog-server/ta-p/3143143 | + + +### Sourcetypes + +| sourcetype | notes | +|----------------|---------------------------------------------------------------------------------------------------------| +| cisco:acs | Aggregation used | + +### Sourcetype and Index Configuration + +| key | sourcetype | index | notes | +|----------------|----------------|----------------|----------------| +| cisco_acs | cisco:acs | netauth | None | + + +### Filter type + +PATTERN MATCH + +### Setup and Configuration + +* No special steps required + +### Options + +| Variable | default | description | +|----------------|----------------|----------------| +| SC4S_LISTEN_CISCO_ACS_TCP_PORT | empty string | Enable a TCP port for this specific vendor product using the number defined expecting RFC5424 format | +| SC4S_LISTEN_CISCO_ACS_UDP_PORT | empty string | Enable a UDP port for this specific vendor product using the number defined expecting RFC5424 format | +| SC4S_ARCHIVE_CISCO_ACS | no | Enable archive to disk for this specific source | +| SC4S_DEST_CISCO_ACS_HEC | no | When Splunk HEC is disabled globally set to yes to enable this specific source | + +### Verification + +Use the following search to validate events are present + +``` +index= sourcetype=cisco:acs +``` + +Verify timestamp, and host values match as expected + + ## Product - ASA (Pre Firepower) | Ref | Link | diff --git a/package/etc/conf.d/filters/cisco/acs.conf b/package/etc/conf.d/filters/cisco/acs.conf new file mode 100644 index 0000000..42e9938 --- /dev/null +++ b/package/etc/conf.d/filters/cisco/acs.conf @@ -0,0 +1,4 @@ + +filter f_cisco_acs { + program("CSCOacs.*"); +}; \ No newline at end of file diff --git a/package/etc/conf.d/log_paths/p_rfc3164-cisco_acs.conf.tmpl b/package/etc/conf.d/log_paths/p_rfc3164-cisco_acs.conf.tmpl new file mode 100644 index 0000000..5666854 --- /dev/null +++ b/package/etc/conf.d/log_paths/p_rfc3164-cisco_acs.conf.tmpl @@ -0,0 +1,109 @@ +# Cisco ACS +{{ $context := dict "port_id" "CISCO_ACS" "parser" "common"}} +{{ tmpl.Exec "t/source_network.t" $context }} + +#This filter uses a field we set to prevent the original messages before aggregation from being +#sent to Splunk +filter f_cisco_acs_complete{ + match("yes", value("ACS.COMPLETE") type(glob)); +}; + +#This parser adds messages from ACS to a context without sending them +#forward to Splunk +parser acs_grouping { + csv-parser( + columns(PID, ACS.num, ACS.seq, MESSAGE) + delimiters(chars(" ")) + flags(greedy) + ); + grouping-by( + scope(program) + key("$PID") + trigger("$(+ ${ACS.seq} 1)" == "${ACS.num}") + sort-key("${ACS.seq}") + aggregate( + value("MESSAGE" "$(implode '' $(context-values ${MESSAGE}))") + value("ACS.COMPLETE" "yes") + ) + timeout(10) + ); +}; + +#The syslog message includes a date with nano seconds and TZ which is not in the header +#So must reparse the date +parser acs_event_time { + csv-parser( + columns(ACS.DATE, ACS.TIME, ACS.TZ, MESSAGE) + delimiters(chars(" ")) + flags(greedy) + ); + + date-parser( + #YYYY- MM-DD hh:mm:ss:xxx +/-zh:zm + format("%Y-%m-%d %H:%M:%S.%f %z" ) + template("${ACS.DATE} ${ACS.TIME} ${ACS.TZ}") + ); +}; +# The following is an inline template; we will use this to generate the actual log path +{{ define "log_path" }} +log { +{{- if eq (.) "yes"}} + source(s_DEFAULT); + filter(f_is_rfc3164); + filter(f_cisco_acs); +{{- end}} +{{- if eq (.) "no"}} + source (s_CISCO_ACS); +{{- end}} + + parser(acs_grouping); + + if { + filter(f_cisco_acs_complete); + parser(acs_event_time); + rewrite { + set("cisco_acs", value("fields.sc4s_vendor_product")); + r_set_splunk_dest_default(sourcetype("cisco:acs"), index("netauth")) + }; + + parser {p_add_context_splunk(key("cisco_acs")); }; + parser (compliance_meta_by_source); + + #We want to unset the fields we won't need, as this is copied into the + #disk queue for network destinations. This can be very disk expensive + #if we don't + rewrite { + set("$(template ${fields.sc4s_template} $(template t_msg_only))" value("MSG")); + unset(value("RAWMSG")); + unset(value("PROGRAM")); + unset(value("PID")); + unset(value("LEGACY_MSGHDR")); + unset(value("EPOCH")); + unset(value("VERSION")); + unset(value("TIMESECFRAC")); + groupunset(values("ACS.*")); + }; + +{{- if ((getenv "SC4S_DEST_SPLUNK_HEC_GLOBAL" "yes") | conv.ToBool) or (conv.ToBool (getenv "SC4S_DEST_CISCO_ACS_HEC" "no") | conv.ToBool) }} + destination(d_hec); +{{- end}} + + {{- if (getenv "SC4S_ARCHIVE_GLOBAL") or (getenv "SC4S_ARCHIVE_CISCO_ACS") }} + destination(d_archive); + {{- end}} + + flags(flow-control,final); + }; + + +}; +{{- end}} + +{{- if or (or (getenv (print "SC4S_LISTEN_CISCO_ACS_TCP_PORT")) (getenv (print "SC4S_LISTEN_CISCO_ACS_UDP_PORT"))) (getenv (print "SC4S_LISTEN_CISCO_ACS_TLS_PORT")) }} + +# Listen on the specified dedicated port(s) for CISCO_ACS traffic + {{tmpl.Exec "log_path" "no" }} +{{- end}} + +# Listen on the default port (typically 514) for CISCO_ACS traffic +{{tmpl.Exec "log_path" "yes" }} diff --git a/package/etc/context_templates/splunk_index.csv b/package/etc/context_templates/splunk_index.csv index eaf846d..12927fb 100644 --- a/package/etc/context_templates/splunk_index.csv +++ b/package/etc/context_templates/splunk_index.csv @@ -11,6 +11,7 @@ #checkpoint_splunk_web,index,netproxy #checkpoint_splunk,index,netops #checkpoint_splunk,index,netops +#cisco_acs,index,netauth #cisco_asa,index,netfw #cisco_ios,index,netops #cisco_ise,index,netauth diff --git a/tests/test_cisco_acs.py b/tests/test_cisco_acs.py new file mode 100644 index 0000000..29bf81f --- /dev/null +++ b/tests/test_cisco_acs.py @@ -0,0 +1,57 @@ +# Copyright 2019 Splunk, Inc. +# +# Use of this source code is governed by a BSD-2-clause-style +# license that can be found in the LICENSE-BSD2 file or at +# https://opensource.org/licenses/BSD-2-Clause +import random + +from jinja2 import Environment + +from .sendmessage import * +from .splunkutils import * + +env = Environment(extensions=['jinja2_time.TimeExtension']) + + +def test_cisco_acs_single(record_property, setup_wordlist, setup_splunk): + host = "{}-{}".format(random.choice(setup_wordlist), random.choice(setup_wordlist)) + + mt = env.from_string( + "{{ mark }} {% now 'utc', '%b %d %H:%M:%S' %} {{ host }} CSCOacs_Passed_Authentications 0765855540 1 0 2019-10-24 21:01:05.028 +00:00 0178632943 5202 NOTICE Device-Administration: Command Authorization succeeded, ACSVersion=acs-5.8.1.4-B.462.x86_64, ConfigVersionId=16489, Device IP Address=10.0.0.93, DestinationIPAddress=10.0.0.10, DestinationPort=49, UserName=nsdevman, CmdSet=[ CmdAV=show CmdArgAV=vpn-sessiondb CmdArgAV=full CmdArgAV=ra-ikev2-ipsec ], Protocol=Tacacs, MatchedCommandSet=fw3, RequestLatency=11, Type=Authorization, Privilege-Level=15, Authen-Type=ASCII, Service=None, User=nsdevman, Port=443, Remote-Address=10.0.0.15, Authen-Method=TacacsPlus, Service-Argument=shell, AcsSessionID=mnsvdcfpiuac03/359448835/9871764, AuthenticationIdentityStore=AD1, AuthenticationMethod=Lookup, SelectedAccessService=Default Device Admin, SelectedCommandSet=fw3, IdentityGroup=IdentityGroup:All Groups:SystemID, Step=13005 , Step=15008 , Step=15004 , Step=15012 , Step=15041 , Step=15004 , Step=15013 , Step=24210 , Step=24212 , Step=24432 , Step=24325 , Step=24313 , Step=24319 , Step=24323 , Step=24420 , Step=24355 , Step=24416 , Step=22037 , Step=15044 , Step=15035 , Step=15042 , Step=15036 , Step=15004 , Step=15018 , Step=13024 , Step=13034 , SelectedAuthenticationIdentityStores=Internal Users, NetworkDeviceName=devicenamehere, NetworkDeviceGroups=Device Type:All Device Types:Firewall:Cisco Systems:Firewall:ASA5545, NetworkDeviceGroups=Location:All Locations:MN, ServiceSelectionMatchedRule=TACACS, IdentityPolicyMatchedRule=Firewall, AuthorizationPolicyMatchedRule=nsdevman, AD-User-Candidate-Identities=nsdevman@ent.example.corp, AD-User-DNS-Domain=ent.example.corp, AD-User-NetBios-Name=AD-ENT, AD-User-Resolved-Identities=nsdevman@ent.example.corp, AD-User-Join-Point=ENT.example.CORP, AD-User-Resolved-DNs=CN=nsdevman\,OU=Service Accounts\,OU=CAO\,OU=ENT\,DC=ent\,DC=wfb\,DC=example\,DC=corp, StepData=10=nsdevman, StepData=11=ent.example.corp, StepData=12=example.corp, StepData=15=ent.example.corp, AD-Domain=ent.example.corp, IdentityAccessRestricted=false, UserIdentityGroup=IdentityGroup:All Groups:SystemID, Cisco-Firewall=Superuser, Firewall=Superuser, NetSec-CSM=User, NetSec-Logging=Engineer, Response={Type=Authorization; Author-Reply-Status=PassAdd; ExternalIdentityStoreName=AD1; }\n") + message = mt.render(mark="<165>", host=host) + sendsingle(message) + + st = env.from_string("search index=netauth host=\"{{ host }}\" sourcetype=\"cisco:acs\" | head 11") + search = st.render(host=host) + + resultCount, eventCount = splunk_single(setup_splunk, search) + + record_property("host", host) + record_property("resultCount", resultCount) + record_property("message", message) + + assert resultCount == 1 + +def test_cisco_acs_multi(record_property, setup_wordlist, setup_splunk): + host = "{}-{}".format(random.choice(setup_wordlist), random.choice(setup_wordlist)) + + mt = env.from_string( + "{{ mark }} {% now 'utc', '%b %d %H:%M:%S' %} {{ host }} CSCOacs_Passed_Authentications 0000000002 2 0 2011-08-01 22:32:53.032 +00:00 0000008450 5203 NOTICE Device-Administration: Session Authorization succeeded, ACSVersion=acs-5.2.0.26-B.3075, ConfigVersionId=117, Device IP Address=192.168.26.137, UserName=edward, CmdSet=[ CmdAV= ], Protocol=Tacacs, RequestLatency=10, NetworkDeviceName=switch, Type=Authorization, Privilege-Level=1, Authen-Type=ASCII, Service=Login, User=edward, Port=tty2, Remote-Address=10.78.167.190, Authen-Method=TacacsPlus, Service-Argument=shell, AcsSessionID=ACS41/101085887/112, AuthenticationIdentityStore=Internal Users, AuthenticationMethod=Lookup, SelectedAccessService=Default Device Admin, SelectedShellProfile=Permit Access, IdentityGroup=IdentityGroup:All Groups, Step=13005 , Step=15008 , Step=15004 , Step=15012 , Step=15041 , Step=15006 , Step=15013 , Step=24210 , Step=24212 , Step=22037 , Step=15044 , Step=15035 , Step=15042 , Step=15036 , Step=15004 , Step=15017 , Step=13034 ,\n") + message = mt.render(mark="<165>", host=host) + sendsingle(message) + + mt = env.from_string( + "{{ mark }} {% now 'utc', '%b %d %H:%M:%S' %} {{ host }} CSCOacs_Passed_Authentications 0000000002 2 1 Step=13015 , SelectedAuthenticationIdentityStores=Internal Users, NetworkDeviceGroups=s1Migrated_NDGs:All s1Migrated_NDGs, NetworkDeviceGroups=Device Type:All Device Types, NetworkDeviceGroups=Location:All Locations, ServiceSelectionMatchedRule=Rule-2, IdentityPolicyMatchedRule=Default, AuthorizationPolicyMatchedRule=Rule-0, Action=Login, Privilege-Level=1, Authen-Type=ASCII, Service=Login, Remote-Address=10.78.167.190, UserIdentityGroup=IdentityGroup:All\n") + message = mt.render(mark="<165>", host=host) + sendsingle(message) + + st = env.from_string("search index=netauth host=\"{{ host }}\" sourcetype=\"cisco:acs\" \"Step=13015\" | head 11") + search = st.render(host=host) + + resultCount, eventCount = splunk_single(setup_splunk, search) + + record_property("host", host) + record_property("resultCount", resultCount) + record_property("message", message) + + assert resultCount == 1 \ No newline at end of file