Showing posts with label App. Show all posts
Showing posts with label App. Show all posts

Monday, June 17, 2019

Parsing and Displaying Okta Data in Splunk - Part III - App Lookup Tool

By Tony Lee

If you are reading this page chances are good that you have both Splunk and Okta. The good news is that there is a pre-built TA (https://splunkbase.splunk.com/app/2806/) to help with the data ingest and parsing, plus an app (https://splunkbase.splunk.com/app/2821/) to help with the visualizations. However, there is always room to improve and thus we created and are sharing some additional lookup dashboards to make the data more actionable.

Figure 1:  At the time of this article, an Okta TA and App exists

The first two articles of this series covered two useful lookup tools:
1)  User Lookup - http://securitysynapse.blogspot.com/2019/06/parsing-and-displaying-okta-data-part-i-user-lookup.html
2)  Group Lookup - http://securitysynapse.blogspot.com/2019/06/parsing-and-displaying-okta-data-part-ii-group-lookup.html

In this third article, we will show how to create an app lookup tool (with group and user drilldown!) using the information contained within the Okta logs. Since Okta has quite a bit of user and group information, the existing data makes a useful Rolodex that is available to Splunk. This is especially useful to a SOC analyst who might be tracking down user or group access based on application name.


Figure 2:  App Lookup Tool created using Okta data!


Data Categorization

Okta data brought in via the TA is easily distinguishable via the source field.  For example:
  • okta:user
  • okta:event
  • okta:group
  • okta:app
Thus, for app data, we will use source=okta:app 


Raw Log

A sample app event is shown below (this should be close to what you see using the TA). Our event also contained two multi-value fields called assigned_users{} which contains the user IDs for the users assigned to that app and assigned_groups{} which contains the group IDs for the groups assigned to that app:

[
  {
    "id": "0oa1gjh63g214q0Hq0g4",
    "name": "testorgone_customsaml20app_1",
    "label": "Custom Saml 2.0 App",
    "status": "ACTIVE",
    "lastUpdated": "2016-08-09T20:12:19.000Z",
    "created": "2016-08-09T20:12:19.000Z",
    "accessibility": {
      "selfService": false,
      "errorRedirectUrl": null,
      "loginRedirectUrl": null
    },
    "visibility": {
      "autoSubmitToolbar": false,
      "hide": {
        "iOS": false,
        "web": false
      },
      "appLinks": {
        "testorgone_customsaml20app_1_link": true
      }
    },
    "features": [],
    "signOnMode": "SAML_2_0",
    "credentials": {
      "userNameTemplate": {
        "template": "${fn:substringBefore(source.login, \"@\")}",
        "type": "BUILT_IN"
      },
      "signing": {}
    },
    "settings": {
      "app": {},
      "notifications": {
        "vpn": {
          "network": {
            "connection": "DISABLED"
          },
          "message": null,
          "helpUrl": null
        }
      },
      "signOn": {
        "defaultRelayState": "",
        "ssoAcsUrl": "https://{yourOktaDomain}",
        "idpIssuer": "http://www.okta.com/${org.externalKey}",
        "audience": "https://example.com/tenant/123",
        "recipient": "http://recipient.okta.com",
        "destination": "http://destination.okta.com",
        "subjectNameIdTemplate": "${user.userName}",
        "subjectNameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
        "responseSigned": true,
        "assertionSigned": true,
        "signatureAlgorithm": "RSA_SHA256",
        "digestAlgorithm": "SHA256",
        "honorForceAuthn": true,
        "authnContextClassRef": "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
        "spIssuer": null,
        "requestCompressed": false,
        "attributeStatements": []
      }
    },
    "_links": {
      "logo": [
        {
          "name": "medium",
          "href": "http://testorgone.okta.com/assets/img/logos/default.6770228fb0dab49a1695ef440a5279bb.png",
          "type": "image/png"
        }
      ],
      "appLinks": [
        {
          "name": "testorgone_customsaml20app_1_link",
          "href": "http://testorgone.okta.com/home/testorgone_customsaml20app_1/0oa1gjh63g214q0Hq0g4/aln1gofChJaerOVfY0g4",
          "type": "text/html"
        }
      ],
      "help": {
        "href": "http://testorgone-admin.okta.com/app/testorgone_customsaml20app_1/0oa1gjh63g214q0Hq0g4/setup/help/SAML_2_0/instructions",
        "type": "text/html"
      },
      "users": {
        "href": "http://testorgone.okta.com/api/v1/apps/0oa1gjh63g214q0Hq0g4/users"
      },
      "deactivate": {
        "href": "http://testorgone.okta.com/api/v1/apps/0oa1gjh63g214q0Hq0g4/lifecycle/deactivate"
      },
      "groups": {
        "href": "http://testorgone.okta.com/api/v1/apps/0oa1gjh63g214q0Hq0g4/groups"
      },
      "metadata": {
        "href": "http://testorgone.okta.com:/api/v1/apps/0oa1gjh63g214q0Hq0g4/sso/saml/metadata",
        "type": "application/xml"
      }
    }
  },
  {
    "id": "0oabkvBLDEKCNXBGYUAS",
    "name": "template_swa",
    "label": "Sample Plugin App",
    "status": "ACTIVE",
    "lastUpdated": "2013-09-11T17:58:54.000Z",
    "created": "2013-09-11T17:46:08.000Z",
    "accessibility": {
      "selfService": false,
      "errorRedirectUrl": null
    },
    "visibility": {
      "autoSubmitToolbar": false,
      "hide": {
        "iOS": false,
        "web": false
      },
      "appLinks": {
        "login": true
      }
    },
    "features": [],
    "signOnMode": "BROWSER_PLUGIN",
    "credentials": {
      "scheme": "EDIT_USERNAME_AND_PASSWORD",
      "userNameTemplate": {
        "template": "${source.login}",
        "type": "BUILT_IN"
      }
    },
    "settings": {
      "app": {
        "buttonField": "btn-login",
        "passwordField": "txtbox-password",
        "usernameField": "txtbox-username",
        "url": "https://example.com/login.html"
      }
    },
    "_links": {
      "logo": [
        {
          "href": "https:/example.okta.com/img/logos/logo_1.png",
          "name": "medium",
          "type": "image/png"
        }
      ],
      "users": {
        "href": "https://{yourOktaDomain}/api/v1/apps/0oabkvBLDEKCNXBGYUAS/users"
      },
      "groups": {
        "href": "https://{yourOktaDomain}/api/v1/apps/0oabkvBLDEKCNXBGYUAS/groups"
      },
      "self": {
        "href": "https://{yourOktaDomain}/api/v1/apps/0oabkvBLDEKCNXBGYUAS"
      },
      "deactivate": {
        "href": "https://{yourOktaDomain}/api/v1/apps/0oabkvBLDEKCNXBGYUAS/lifecycle/deactivate"
      }
    }
  }
]

Source:  https://developer.okta.com/docs/api/resources/apps#list-applications

Fields we need to parse

Fortunately, the available TA already parses the data for us, but the fields that we are most interested in for this lookup dashboard are the following:
  • dest
  • name
  • label
  • signOnMode
  • created
  • lastUpdated
  • status
  • assigned_users{}
  • assigned_groups{}
Feel free to modify the search and replace fields as needed.

Search String

A simple search string that gets us the table needed is shown below. We deduplicated the results by app id since it is a unique field. We also added a count of the number of users (assigned_users) and groups (assigned_groups) in each app. Now just add filters such as the ones we provided in our dashboard code at the end of the article and you are in business! 

index=okta source=okta:app | dedup id | eval assigned_users=mvcount('assigned_users{}') | eval assigned_groups=mvcount('assigned_groups{}') | fillnull value=0 assigned_users, assigned_groups | table dest, name, label, signOnMode, assigned_groups, assigned_users, created, lastUpdated, status, assigned_users{}, assigned_groups{}

The dashboard code we included below also contains a user and group drilldown to reveal the users and groups assigned to selected app. Simply click the row of the application you are interested in and it will show you the users and groups assigned to that app. This interactive drilldown pulls the assigned_users{} and assigned_groups{} multi-value fields and performs a user lookup (source=okta:user and source=okta:group) as seen in the previous article. Note, we also use a clever <fields> trick to hide the assigned_users{} and assigned_groups{} columns, while still making that data usable in the drilldown.

Conclusion

Even though we had a Splunk TA and App to perform the parsing and help create visibility, we extended the usefulness of the data to build an app lookup tool with a user and group drilldown. We hope this article helps others gain additional insight into their user and group data via Okta logs. Happy Splunking!

Dashboard Code

The following dashboard assumes that the appropriate logs are being collected and sent to Splunk. Additionally, the dashboard code assumes an index of okta. Feel free to adjust as necessary. Splunk dashboard code provided below:


<form>
  <label>Okta App Lookup</label>
  <description>index=okta source=okta:app   (First try last 6 hours, then try a longer time range)</description>
  <fieldset autoRun="true" submitButton="true">
    <input type="time" token="time">
      <label>Time Range</label>
      <default>
        <earliest>-6h@h</earliest>
        <latest>now</latest>
      </default>
    </input>
    <input type="text" token="wild">
      <label>Wildcard Search</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="text" token="name">
      <label>Name (Exact match)</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="text" token="label">
      <label>Label (Exact Match)</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="dropdown" token="status">
      <label>Status</label>
      <choice value="*">ALL</choice>
      <choice value="ACTIVE">ACTIVE</choice>
      <choice value="INACTIVE">INACTIVE</choice>
      <default>ACTIVE</default>
      <initialValue>ACTIVE</initialValue>
    </input>
  </fieldset>
  <row>
    <panel>
      <table>
        <title>App Details</title>
        <search>
          <query>index=okta source=okta:app $wild$ name=$name$ label=$label$ status=$status$ | dedup id | eval assigned_users=mvcount('assigned_users{}') | eval assigned_groups=mvcount('assigned_groups{}') | fillnull value=0 assigned_users, assigned_groups | table dest, name, label, signOnMode, assigned_groups, assigned_users, created, lastUpdated, status, assigned_users{}, assigned_groups{}</query>
          <earliest>-6h@h</earliest>
          <latest>now</latest>
          <sampleRatio>1</sampleRatio>
        </search>
        <option name="dataOverlayMode">none</option>
        <option name="drilldown">cell</option>
        <option name="percentagesRow">false</option>
        <option name="rowNumbers">false</option>
        <option name="totalsRow">false</option>
        <option name="wrap">true</option>
        <fields>["dest","name","label","signOnMode","assigned_groups","assigned_users","created","lastUpdated","status"]</fields>
        <drilldown>
          <set token="users">$row.assigned_users{}$</set>
          <set token="groups">$row.assigned_groups{}$</set>
        </drilldown>
      </table>
    </panel>
  </row>
  <row>
    <panel>
      <table>
        <title>Assigned Groups (Click a row above to fetch the groups assigned to the app)</title>
        <search>
          <query>| stats count as id | eval id=split("$groups$", ",") | mvexpand id | join type=left id [search index=okta source=okta:group id IN ($groups$)] | eval num_members=mvcount('members{}') | fillnull value=0 num_members | table dest, type, profile.groupScope, profile.windowsDomainQualifiedName, profile.name, profile.description, created, lastUpdated, lastMembershipUpdated, num_members</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="drilldown">none</option>
        <option name="rowNumbers">true</option>
      </table>
    </panel>
  </row>
  <row>
    <panel>
      <table>
        <title>Assigned Users (Click a row above to fetch the users assigned to the app)</title>
        <search>
          <query>| stats count as id | eval id=split("$users$", ",") | mvexpand id | join type=left id [search index=okta source=okta:user id IN ($users$) | table id, credentials.provider.type, profile.title, profile.firstName, profile.middleName, profile.lastName, profile.email, profile.primaryPhone, created, passwordChanged, lastLogin, status]</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="drilldown">none</option>
        <option name="rowNumbers">true</option>
      </table>
    </panel>
  </row>
</form>

Friday, June 14, 2019

Parsing and Displaying Okta Data in Splunk - Part II - Group Lookup Tool

By Tony Lee

If you are reading this page chances are good that you have both Splunk and Okta. The good news is that there is a pre-built TA (https://splunkbase.splunk.com/app/2806/) to help with the data ingest and parsing, plus an app (https://splunkbase.splunk.com/app/2821/) to help with the visualizations. However, there is always room to improve and thus we created and are sharing some additional lookup dashboards to make the data more actionable.

Figure 1:  At the time of this article, an Okta TA and App exists

We shared a user lookup tool in part I of the series: http://securitysynapse.blogspot.com/2019/06/parsing-and-displaying-okta-data-part-i-user-lookup.html

In this second article, we will show how to create a group lookup tool (with member drilldown!) using the information contained within the Okta logs. Since Okta has quite a bit of user and group information, the existing data makes a useful Rolodex (again, ask your parents) that is available to Splunk. This is especially useful to a SOC analyst who might be tracking down a user or group.

Figure 2:  Group Lookup Tool created using Okta data!


Data Categorization

Okta data brought in via the TA is easily distinguishable via the source field.  For example:
  • okta:user
  • okta:event
  • okta:group
  • okta:app
Thus, for group data, we will use source=okta:group 


Raw Log

A sample group event is shown below. Our event also contained a multi-value field called members{} which contains the user ID for the members of that group:

{
  "id": "00g1emaKYZTWRYYRRTSK",
  "created": "2015-02-06T10:11:28.000Z",
  "lastUpdated": "2015-10-05T19:16:43.000Z",
  "lastMembershipUpdated": "2015-11-28T19:15:32.000Z",
  "objectClass": [
    "okta:user_group"
  ],
  "type": "OKTA_GROUP",
  "profile": {
    "name": "West Coast Users",
    "description": "All Users West of The Rockies"
  },
  "_links": {
    "logo": [
      {
        "name": "medium",
        "href": "https://{yourOktaDomain}/img/logos/groups/okta-medium.png",
        "type": "image/png"
      },
      {
        "name": "large",
        "href": "https://{yourOktaDomain}/img/logos/groups/okta-large.png",
        "type": "image/png"
      }
    ],
    "users": {
      "href": "https://{yourOktaDomain}/api/v1/groups/00g1emaKYZTWRYYRRTSK/users"
    },
    "apps": {
      "href": "https://{yourOktaDomain}/api/v1/groups/00g1emaKYZTWRYYRRTSK/apps"
    }
  }
}

Source:  https://developer.okta.com/docs/api/resources/groups#get-group

Fields we need to parse

Fortunately, the available TA already parses the data for us, but the fields that we are most interested in for this lookup dashboard are the following:
  • type
  • groupScope
  • windowsDomainQualifiedName
  • name
  • description
  • created
  • lastUpdated
  • lastMembershipUpdated
  • members{}
Feel free to modify the search and replace fields as needed.

Search String

A simple search string that gets us the table needed is shown below. We deduplicated the results by id since it is a unique field. We also added a count of the number of members (num_members) in each group. Now just add filters such as the ones we provided in our dashboard code at the end of the article and you are in business! 

index=okta source=okta:group | dedup id | eval num_members=mvcount('members{}') | fillnull value=0 num_members | table dest, type, profile.groupScope, profile.windowsDomainQualifiedName, profile.name, profile.description, created, lastUpdated, lastMembershipUpdated, num_members

The dashboard code we included below also contains a group drilldown to reveal the users in the group. Simply click the row of the group you are interested in and it will show you the users in that group. This clever drilldown pulls the members{} multi-value field and performs a user lookup (source=okta:user) as seen in the previous article. Note, we also use a clever <fields> trick to hide the members{} column, but still make it usable in the drilldown.

Conclusion

Even though we had a Splunk TA and App to perform the parsing and help create visibility, we extended the usefulness of the data to build a group lookup tool with member drilldown. We hope this article helps others gain additional insight into their user and group data via Okta logs. Happy Splunking!

Dashboard Code

The following dashboard assumes that the appropriate logs are being collected and sent to Splunk. Additionally, the dashboard code assumes an index of okta. Feel free to adjust as necessary. Splunk dashboard code provided below:


<form>
  <label>Okta Group Lookup</label>
  <description>index=okta source=okta:group   (First try last 6 hours, then try a longer time range)</description>
  <fieldset autoRun="true" submitButton="true">
    <input type="time" token="time">
      <label>Time Range</label>
      <default>
        <earliest>-6h@h</earliest>
        <latest>now</latest>
      </default>
    </input>
    <input type="text" token="wild">
      <label>Wildcard Search</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="text" token="name">
      <label>Group Name (Exact match)</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="text" token="description">
      <label>Group Description</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="dropdown" token="exclude_empty_groups">
      <label>Exclude Empty Groups</label>
      <choice value="members{}=*">Yes</choice>
      <choice value="">No</choice>
      <default>members{}=*</default>
      <initialValue>members{}=*</initialValue>
    </input>
  </fieldset>
  <row>
    <panel>
      <table>
        <title>Group Details</title>
        <search>
          <query>index=okta source=okta:group $wild$ profile.name=$name$ profile.description=*$description$* $exclude_empty_groups$ | dedup id | eval num_members=mvcount('members{}') | fillnull value=0 num_members | table dest, type, profile.groupScope, profile.windowsDomainQualifiedName, profile.name, profile.description, created, lastUpdated, lastMembershipUpdated, num_members, members{}</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
          <sampleRatio>1</sampleRatio>
        </search>
        <option name="count">10</option>
        <option name="dataOverlayMode">none</option>
        <option name="drilldown">cell</option>
        <option name="percentagesRow">false</option>
        <option name="refresh.display">progressbar</option>
        <option name="rowNumbers">false</option>
        <option name="totalsRow">false</option>
        <option name="wrap">true</option>
        <fields>["dest","type","profile.groupScope","profile.windowsDomainQualifiedName","profile.name","profile.description","created","lastUpdated","lastMembershipUpdated","num_members"]</fields>
        <drilldown>
          <set token="members">$row.members{}$</set>
        </drilldown>
      </table>
    </panel>
  </row>
  <row>
    <panel>
      <table>
        <title>Members (Click a row above to fetch the members of the group)</title>
        <search>
          <query>| stats count as id | eval id=split("$members$", ",") | mvexpand id | join type=left id [search index=okta source=okta:user id IN ($members$) | table id, credentials.provider.type, profile.title, profile.firstName, profile.middleName, profile.lastName, profile.email, profile.primaryPhone, created, passwordChanged, lastLogin, status]</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="drilldown">none</option>
        <option name="rowNumbers">true</option>
      </table>
    </panel>
  </row>
</form>

Wednesday, June 12, 2019

Parsing and Displaying Okta Data in Splunk - Part I - User Lookup Tool

By Tony Lee

If you are reading this page chances are good that you have both Splunk and Okta. The good news is that there is a pre-built TA (https://splunkbase.splunk.com/app/2806/) to help with the data ingest and parsing, plus an app (https://splunkbase.splunk.com/app/2821/) to help with the visualizations. However, there is always room to improve and thus we created and are sharing some additional lookup dashboards to make the data more actionable.

Figure 1:  At the time of this article, an Okta TA and App exists

In this first article, we will show how to create a user lookup tool using the information contained within the Okta logs. As a bonus, we will provide our dashboard board at the bottom. Since Okta has quite a bit of user information, the existing data makes a useful Rolodex (yes, that paper-based wheel of information--ask your parents if still unclear) that is available to Splunk. This is especially useful to a SOC analyst who might be tracking down a user or group.

Figure 2:  Okta User Lookup dashboard with useful filters!


Data Categorization

Okta data brought in via the TA is easily distinguishable via the source field.  For example:
  • okta:user
  • okta:event
  • okta:group
  • okta:app
Thus, for user data, we will use source=okta:user 


Raw Log

A sample user event is shown below. Notice how much useful data is contained within the event:

[
  {
    "id": "00ub0oNGTSWTBKOLGLNR",
    "status": "STAGED",
    "created": "2013-07-02T21:36:25.344Z",
    "activated": null,
    "statusChanged": null,
    "lastLogin": null,
    "lastUpdated": "2013-07-02T21:36:25.344Z",
    "passwordChanged": "2013-07-02T21:36:25.344Z",
    "profile": {
      "firstName": "Isaac",
      "lastName": "Brock",
      "email": "isaac.brock@example.com",
      "login": "isaac.brock@example.com",
      "mobilePhone": "555-415-1337"
    },
    "credentials": {
      "provider": {
        "type": "OKTA",
        "name": "OKTA"
      }
    },
    "_links": {
      "self": {
        "href": "https://{yourOktaDomain}/api/v1/users/00ub0oNGTSWTBKOLGLNR"
      }
    }
  },
  {
    "id": "00ub0oNGTSWTBKOLGLNR",
    "status": "ACTIVE",
    "created": "2013-06-24T16:39:18.000Z",
    "activated": "2013-06-24T16:39:19.000Z",
    "statusChanged": "2013-06-24T16:39:19.000Z",
    "lastLogin": "2013-06-24T17:39:19.000Z",
    "lastUpdated": "2013-07-02T21:36:25.344Z",
    "passwordChanged": "2013-07-02T21:36:25.344Z",
    "profile": {
      "firstName": "Eric",
      "lastName": "Judy",
      "email": "eric.judy@example.com",
      "secondEmail": "eric@example.org",
      "login": "eric.judy@example.com",
      "mobilePhone": "555-415-2011"
    },
    "credentials": {
      "password": {},
      "recovery_question": {
        "question": "The stars are projectors?"
      },
      "provider": {
        "type": "OKTA",
        "name": "OKTA"
      }
    },
    "_links": {
      "self": {
        "href": "https://{yourOktaDomain}/api/v1/users/00ub0oNGTSWTBKOLGLNR"
      }
    }
  }
]


Source:  https://developer.okta.com/docs/api/resources/users#list-users

Fields we need to parse

Fortunately, the available TA already parses the data for us, but the fields that we are most interested in for this lookup dashboard are the following:
  • title
  • firstName
  • middleName
  • lastName
  • email
  • primaryPhone
  • created
  • passwordChanged
  • lastLogin
  • status
Feel free to modify the search and replace fields as needed.

Search String

A simple search string that gets us the table needed is shown below. We deduplicated the results by id since it is a unique field. Now just add filters such as the ones we provided in our dashboard code at the end of the article and you are in business! 

index=okta source=okta:user | dedup id | table credentials.provider.type, profile.title, profile.firstName, profile.middleName, profile.lastName, profile.email, profile.primaryPhone, created, passwordChanged, lastLogin, status

Conclusion

Even though we had a Splunk TA and App to perform the parsing and help create visibility, we extended the usefulness of the data to build a user lookup tool. We hope this article helps others gain additional insight into their user data via Okta logs. Happy Splunking!

Dashboard Code

The following dashboard assumes that the appropriate logs are being collected and sent to Splunk. Additionally, the dashboard code assumes an index of okta. Feel free to adjust as necessary. Splunk dashboard code provided below:


<form>
  <label>Okta User Lookup</label>
  <description>index=okta source=okta:user   (First try last 24 hours, then try a longer time range)</description>
  <fieldset autoRun="true" submitButton="true">
    <input type="time" token="time">
      <label>Time Range</label>
      <default>
        <earliest>-24h@h</earliest>
        <latest>now</latest>
      </default>
    </input>
    <input type="text" token="wild">
      <label>Wildcard Search</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="text" token="first">
      <label>First Name</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="text" token="last">
      <label>Last Name</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
    <input type="text" token="email">
      <label>Email Address</label>
      <default>*</default>
      <initialValue>*</initialValue>
    </input>
  </fieldset>
  <row>
    <panel>
      <table>
        <title>Details</title>
        <search>
          <query>index=okta source=okta:user $wild$ profile.firstName=$first$ profile.lastName=$last$ profile.email=$email$ | dedup id | table credentials.provider.type, profile.title, profile.firstName, profile.middleName, profile.lastName, profile.email, profile.primaryPhone, created, passwordChanged, lastLogin, status</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
          <sampleRatio>1</sampleRatio>
        </search>
        <option name="count">10</option>
        <option name="dataOverlayMode">none</option>
        <option name="drilldown">none</option>
        <option name="percentagesRow">false</option>
        <option name="rowNumbers">false</option>
        <option name="totalsRow">false</option>
        <option name="wrap">true</option>
      </table>
    </panel>
  </row>
</form>

Wednesday, January 2, 2019

Parsing and Displaying Cisco ISE Data in Splunk

By Tony Lee

If you are reading this page chances are good that you have both Splunk and Cisco Identity Services Engine (ISE). Chances are also pretty good that you have seen what a challenge it can be to parse these logs and make them into a useful dashboard. Granted, there is probably one app and TA combination out of the 50+ Cisco Apps and TAs on Splunkbase that will work for you, but if you strike out there, you can always try the solution and dashboard provided in the article below.

To get started, you should at least install the TA found here to parse the fields: http://splunkbase.splunk.com/app/1915 and give your incoming Cisco ISE syslog stream a sourcetype of "cisco:ise:syslog" per the documentation here:  http://docs.splunk.com/Documentation/AddOns/released/CiscoISE/Datatypes.  If you have data flowing and the fields are parsed out, we are in business.

Now, hold on to your hats, we are about to dive into the world of Cisco ISE logs and figure out just how to create the following dashboard.


Figure 1:  A useful Cisco ISE dashboard with all necessary data.

Caveat:  This article assumes that you called your Cisco ISE index "cisco-ise".  If you did not, just change the commands and dashboard to fit your index name.

The Problem

As mentioned in the introduction, the logs are a bit messy. The upside is that they are data rich. There is so much that you can extract from the logs, but first you need to piece them back together -- literally. The logs are sent over in chunks as shown below:

2019-08-06T16:33:06+00:00 HOST CISE_Passed_Authentications 0000649495 4 3  ....

2019-08-06T16:33:06+00:00 HOST CISE_Passed_Authentications 0000649495 4 2  ....

2019-08-06T16:33:06+00:00 HOST CISE_Passed_Authentications 0000649495 4 1  ....

2019-08-06T16:33:06+00:00 HOST CISE_Passed_Authentications 0000649495 4 0  ....

Here is the kicker, those four events are related (as indicated by the first large number which we are calling an event_id and then the next two numbers, the last of which increments). When combined into one event, it contains a ton of data. So, how do we combine the events? 


The Solution

Fortunately, Splunk has a transaction function that we can use to indicate that the events are related and should be combined into one event.  But we have a problem, that field is not parsed by the Splunk TA mentioned in the introduction, so we will need to parse it.

We can parse it with the following gnarly regex:
^(?:[^ \n]* ){3}(?P<event_id>\d+)\s+

Figure 2:  event_id parsed using a Splunk field extraction

With the event_id parsed, we can now use the transaction statement to combine the four events into one event which can be seen with the following search command:

index=cisco-ise | transaction event_id

Now, let's take it a bit farther and table the most interesting fields (feel free to leave a comment if you feel that we left out an interesting field):

index=cisco-ise | transaction event_id | table _time, host, event_id, NetworkDeviceName, NAS_IP_Address, NAS_Port, Location, SelectedAuthenticationIdentityStores, SelectedAuthorizationProfiles, SSID, ISEPolicySetName, UserName, EndPointMACAddress, Calling_Station_ID, Called_Station_ID

Conclusion

We hope that this article has been helpful in understanding Cisco ISE logs and how to combine them to extract feature rich data from single events. As always, happy Splunking!

Dashboard Code

The dashboard code below assumes the index is cisco-ise and the Cisco TA is properly parsing the data. Please adjust as necessary.

<form>
  <label>Cisco ISE</label>
  <description>Populated by syslog data</description>
  <fieldset submitButton="true" autoRun="false">
    <input type="time" token="time" searchWhenChanged="true">
      <label>Time Range</label>
      <default>
        <earliest>-8h@h</earliest>
        <latest>now</latest>
      </default>
    </input>
    <input type="text" token="wild">
      <label>Wildcard</label>
      <default>*</default>
    </input>
  </fieldset>
  <row>
    <panel>
      <table>
        <title>Top Network Device Name</title>
        <search>
          <query>index=cisco-ise | transaction event_id | table _time, host, event_id, NetworkDeviceName, NAS_IP_Address, NAS_Port, Location, SelectedAuthenticationIdentityStores, SelectedAuthorizationProfiles, SSID, ISEPolicySetName, UserName, EndPointMACAddress, Calling_Station_ID, Called_Station_ID | search $wild$ | top limit=0 NetworkDeviceName</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="count">10</option>
        <option name="drilldown">cell</option>
        <option name="refresh.display">progressbar</option>
      </table>
    </panel>
    <panel>
      <table>
        <title>Top Location</title>
        <search>
          <query>index=cisco-ise | transaction event_id | table _time, host, event_id, NetworkDeviceName, NAS_IP_Address, NAS_Port, Location, SelectedAuthenticationIdentityStores, SelectedAuthorizationProfiles, SSID, ISEPolicySetName, UserName, EndPointMACAddress, Calling_Station_ID, Called_Station_ID | search $wild$ | top limit=0 Location</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="count">10</option>
        <option name="drilldown">cell</option>
        <option name="refresh.display">progressbar</option>
      </table>
    </panel>
    <panel>
      <table>
        <title>Top ISE Policy Set Name</title>
        <search>
          <query>index=cisco-ise | transaction event_id | table _time, host, event_id, NetworkDeviceName, NAS_IP_Address, NAS_Port, Location, SelectedAuthenticationIdentityStores, SelectedAuthorizationProfiles, SSID, ISEPolicySetName, UserName, EndPointMACAddress, Calling_Station_ID, Called_Station_ID | search $wild$ | top limit=0 ISEPolicySetName</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="count">10</option>
        <option name="drilldown">cell</option>
        <option name="refresh.display">progressbar</option>
      </table>
    </panel>
    <panel>
      <table>
        <title>Top User Name</title>
        <search>
          <query>index=cisco-ise | transaction event_id | table _time, host, event_id, NetworkDeviceName, NAS_IP_Address, NAS_Port, Location, SelectedAuthenticationIdentityStores, SelectedAuthorizationProfiles, SSID, ISEPolicySetName, UserName, EndPointMACAddress, Calling_Station_ID, Called_Station_ID | search $wild$ | top limit=0 UserName</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="count">10</option>
        <option name="drilldown">cell</option>
        <option name="refresh.display">progressbar</option>
      </table>
    </panel>
  </row>
  <row>
    <panel>
      <table>
        <title>Details</title>
        <search>
          <query>index=cisco-ise | transaction event_id | table _time, host, event_id, NetworkDeviceName, NAS_IP_Address, NAS_Port, Location, SelectedAuthenticationIdentityStores, SelectedAuthorizationProfiles, SSID, ISEPolicySetName, UserName, EndPointMACAddress, Calling_Station_ID, Called_Station_ID | search $wild$</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
          <sampleRatio>1</sampleRatio>
        </search>
        <option name="count">10</option>
        <option name="dataOverlayMode">none</option>
        <option name="drilldown">cell</option>
        <option name="percentagesRow">false</option>
        <option name="refresh.display">progressbar</option>
        <option name="rowNumbers">false</option>
        <option name="totalsRow">false</option>
        <option name="wrap">true</option>
      </table>
    </panel>
  </row>
</form>