Showing posts with label lookup. Show all posts
Showing posts with label lookup. Show all posts

Monday, July 1, 2019

Quick and Flexible IOC Hunting in Splunk

By Tony Lee and Arjun Mathew

Imagine that you are battling a known threat actor.  You have gathered indicators of compromise (IOCs) from reversing malware as well as helpful contributions from the rest of the security community.  But how could those IOCs be tasked across your existing data quickly in order to track attacker movement in real-time?  Here is one possible solution:
  1. Use a lookup file
  2. Clever Splunk search
  3. Even more clever dashboard
This article will outline the process and even share an example dashboard (shown in the screenshot below).

Figure 1:  Known IOC Dashboard provided at the end of the article

Lookup File

We used the following process to create a lookup file and definition.  Create a file in excel and save it as a CSV called known_iocs.csv (similar to the file below).

Figure 2:  CSV that we initially populated with our IOCs

Then within Splunk, navigate to the following to create the lookup and the definition:

Settings > Lookups > Lookup table files > Add new

  • Destination app:  Select the app
  • Upload a lookup file:  known_iocs.csv
  • Destination filename:  known_iocs.csv


Settings > Lookups > Lookup definitions > Add new

  • Destination app:  Select the app
  • Name:  known_iocs.csv
  • Type:  File-based
  • Lookup file:  known_iocs.csv


Now, here is the problem.  How do you scale this solution to a group effort to update a lookup table with IOCs?  It does not work well to pass the CSV around and then constantly upload.  Enter another graceful solution from Luke Murphey -- The Lookup File Editor Splunk App (https://splunkbase.splunk.com/app/1724/).

Figure 3:  Lookup File Editor App from Luke Murphey

Once the Lookup File Editor Splunk App is installed, navigate to it, search for your known_ioc.csv file.  Open it and right click on the bottom line and "Insert a new row".  You can edit the lookup file right in Splunk.  Once it is saved, the correlation searches will automatically run with the new IOC data.


Figure 4:  Inserting a new line to our known_ioc.csv file

Clever Search

Now that we have a lookup table that has our IOCs in it and a convenient way to edit it, we just need a search that will apply the IOCs to our data.  The example below applies the IOCs to the cylance_protect index, but feel free to change the index name as needed.  Additionally, we show how to search just one column of the IOC data as well as multiple columns.


One type of IOC (Hash):

index=cylance_protect [|inputlookup known_iocs.csv | rename Hash as query | table query] | stats count


Two types of IOCs (Hash & FileName)

index=cylance_protect [|inputlookup known_iocs.csv | rename Hash as query | table query] OR [|inputlookup known_iocs.csv | rename FileName as query | table query] | stats count

Note the OR statement between the two inputlookups -- needed when querying multiple columns.


Figure 5:  What will be our top panels showing a count of the hits


Even More Clever Dashboard

Now that we have functional searches, we need a dashboard to monitor our different data feeds such as:
  • Proxy
  • Firewalls
  • DNS
  • Antivirus Hits
  • Email Protection
  • Windows Event Logs

You can see in the screenshot below that we use Single Value panels on the top row.  Each of these panels contains a dynamic drilldown to populate the panel below it with the contents of the Single Value panel when clicked.


Figure 6:  Dashboard displayed at the start of the article and in the Sample Dashboard section below

The drilldown for each Single Value panel sets a token which is essentially the search, but without the stats count (feel free to table the data as needed):

        <drilldown>
          <set token="alert">index=proxy $wild$ [|inputlookup known_iocs.csv | rename Domain as query | table query] | table _raw</set>
        </drilldown>



Then the bottom panel is just a search of the token set in the drilldown above.

        <search>
          <query>| search $alert$</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>

Conclusion

Using a clever combination of features that already exist within Splunk (for the most part), we were able to create a quick method to update an IOC list and apply it against existing data within Splunk. Simply monitor these dashboards and use it to track the attacker's activities in real-time.


Sample Dashboard

The sample dashboard below uses a number of indexes to search over different data feeds.  Just change these indexes to the ones you are interested in monitoring.


<form>
  <label>Known IOC Hits</label>
  <description>Threat Actor</description>
  <fieldset submitButton="true">
    <input type="time" searchWhenChanged="true" token="time">
      <label>Time Range</label>
      <default>
        <earliest>-24h@h</earliest>
        <latest>now</latest>
      </default>
    </input>
    <input type="text" searchWhenChanged="true" token="wild">
      <label>Wildcard Search</label>
      <default>*</default>
    </input>
  </fieldset>
  <row>
    <panel>
      <single>
        <title>Proxy</title>
        <search>
          <query>index=proxy $wild$ [|inputlookup known_iocs.csv | rename Domain as query | table query] | stats count</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="colorMode">none</option>
        <option name="drilldown">all</option>
        <option name="rangeColors">["0x65a637","0xd93f3c"]</option>
        <option name="rangeValues">[0]</option>
        <option name="useColors">1</option>
        <drilldown>
          <set token="alert">index=proxy $wild$ [|inputlookup known_iocs.csv | rename Domain as query | table query] | table _raw</set>
        </drilldown>
      </single>
    </panel>
    <panel>
      <single>
        <title>Firewalls</title>
        <search>
          <query>index=firewalls $wild$ [|inputlookup known_iocs.csv | rename IP as query | table query] | stats count</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="drilldown">all</option>
        <option name="rangeColors">["0x65a637","0xd93f3c"]</option>
        <option name="rangeValues">[0]</option>
        <option name="refresh.display">progressbar</option>
        <option name="useColors">1</option>
        <drilldown>
          <set token="alert">index=firewalls $wild$ [|inputlookup known_iocs.csv | rename IP as query | table query] | table _raw</set>
        </drilldown>
      </single>
    </panel>
    <panel>
      <single>
        <title>DNS</title>
        <search>
          <query>index=dns $wild$ [|inputlookup known_iocs.csv | rename Domain as query | table query] | stats count</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="drilldown">all</option>
        <option name="rangeColors">["0x65a637","0xd93f3c"]</option>
        <option name="rangeValues">[0]</option>
        <option name="useColors">1</option>
        <drilldown>
          <set token="alert">index=dns $wild$ [|inputlookup known_iocs.csv | rename Domain as query | table query] | table _raw</set>
        </drilldown>
      </single>
    </panel>
    <panel>
      <single>
        <title>Antivirus Hits</title>
        <search>
          <query>index=av $wild$ [|inputlookup known_iocs.csv | rename Hash as query | table query] | stats count</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="drilldown">all</option>
        <option name="rangeColors">["0x65a637","0xd93f3c"]</option>
        <option name="rangeValues">[0]</option>
        <option name="useColors">1</option>
        <drilldown>
          <set token="alert">index=av $wild$ [|inputlookup known_iocs.csv | rename Hash as query | table query] | table _raw</set>
        </drilldown>
      </single>
    </panel>
    <panel>
      <single>
        <title>Email Protection</title>
        <search>
          <query>index=mail_protection $wild$ [|inputlookup known_iocs.csv | rename Domain as query | table query] | stats count</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="drilldown">all</option>
        <option name="rangeColors">["0x65a637","0xd93f3c"]</option>
        <option name="rangeValues">[0]</option>
        <option name="useColors">1</option>
        <drilldown>
          <set token="alert">index=mail_protection $wild$ [|inputlookup known_iocs.csv | rename Domain as query | table query] | table _raw</set>
        </drilldown>
      </single>
    </panel>
  </row>
  <row>
    <panel>
      <title>Information Table (Click one of the numbers above to populate this table with Details)</title>
      <table>
        <search>
          <query>| search $alert$</query>
          <earliest>$time.earliest$</earliest>
          <latest>$time.latest$</latest>
        </search>
        <option name="drilldown">cell</option>
      </table>
    </panel>
  </row>
</form>

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>