Wednesday, October 14, 2020

User Aware Splunk Dashboards

One of the more interesting aspects of Splunk is giving users direct access to raw data. This is great on so many levels from a troubleshooting or investigative perspective. However there are times when you'd rather or need to give people, let's call it, a guided experience to what data they see. This is particularly true when within the same overarching or umbrella organization you have separate lines of business or groups of people such that you don't want to give people direct access to the data; you want/need to limit access at more of the UI level. You might have some data source like a vulnerability scanner where all of the data is coming into one index and you want to give people access to only the scan data that applies to them. One way to accomplish this is by adding search time restrictions to particular roles. While effective this approach can get complex very quickly. The following Splunk .conf talk gets into some great detail (link).  Another approach is to slice and dice what index the data goes into as it is indexed based on the user groups you have setup. This can be effective as well but then data from that singular tool is all over the place and what happens if you are using something like CIDR blocks to map data to index and those CIDR blocks change? In this article I'm going to get into a third approach that is making the dashboard user aware and displays information based on who the user is without giving them native access to the data.

I should say there are likely several ways to accomplish this that might be more efficient or work better for particular use cases. This worked for me though and can be a good starting point. If you know of other ways to limit data access at the UI level I'd love to hear about them; feel free to put them in the comments!

To start we are going to lean on/in on the following

  1. Leverage the ability to have a saved search run as the search owner vs search user
  2. The dashboard is going to pass variables/tokens into the saved search based on who the user is
  3. Use a lookup to map groups of users to specific data
In order to demo this I'm going to use internal Splunk data to make this somewhat 'run anywhere' and allow you to create this in your environment. Of course the resulting queries aren't game changers so you will need to use a bit of imagination. 

Use Case - What I want to do is give people in group 1 access to the scheduler logs and group 2 to the splunkd_ui_access logs. 

While I'm using sourcetypes, the concept could still be used for a range of values in a particular field within the same sourcetype: CIDR blocks, building names, names of servers, whatever. To facilitate the ability to dynamically accommodate multiple objects being passed into the saved search I'm going to leverage the "IN" operator. This powerful boolean operator was added into Splunk several years ago and can be used instead of writing out a series of "OR" statements. 

index=_internal sourcetype=scheduler OR sourcetype=splunkd_ui_access

becomes

index=_internal sourcetype IN(scheduler, splunkd_ui_access)

So let's start building!

....I should note there isn't really a set order in how you go through these steps. Just think of them as being all of the pieces that need to be brought together. 

Step 1 - create the lookup to map user roles to data

Nothing fancy here for this use case. I won't go into how to bring this lookup into Splunk.

role,sourcetype

group1,scheduler

group2,splunkd_ui_access



Step 2 - create roles

There are a few ways you can map your users to the data you want them to see. Roles are just one of those ways. In this case I'm simply going to create a role 'shell' which has a name but no real capabilities or limitations. Then you can assign these roles to the users. In this case I'm just creating "group1" and "group2" as that is what I put in my lookup. You will likely already have roles in place that you can use.



For this type of use case it can be useful to prepend a common string in the role name to make subsequent queries easier as I will show later. I've also added both of these roles to the admin account for testing purposes (this is just on my laptop instance of Splunk).

Step 3 - create the search to identify who is looking at the dashboard

To figure out who the user is that is looking at the dashboard I'm going to leverage the /services/authentication/current-context rest endpoint. There are a several ways to write this query. I'm going to use the following and leave it 'horizontal' for space

| rest splunk_server=local /services/authentication/current-context | stats values(roles) as roles | mvexpand roles | search roles=group* | lookup log_access.csv role as roles | stats values(sourcetype) as sourcetype delim="," | nomv sourcetype

So what does this query do? The first bit is just finding out who the user is. Now we probably don't need the stats command but I wanted to make sure I get all the roles in case there is some edge case where multiple lines show up (not likely). The mvexpand command splits out the results so that you get one line per role which allows me to then get just the roles I'm interested in with the search command that follows. At this point we can map each of these roles back to our lookup. Now we need to collapse down the sourcetypes we are going to pass to our saved searches in a way that is comma delimited. The fancy stats command delim bit gets us that. Interestingly enough you can't see that unless you do the nomv command after and if there is only 1 item Splunk is smart enough to realize you don't need the comma!

Once the query is to your liking go ahead and add that to a dashboard. I normally keep that query panel at the top of the dashboard (and we will hide it later).

Step 4 - create the saved searches

For this use case I'm just going to have 2 panels - one showing the number of all events and the other showing the number of events by sourcetype and user. I'll create and save those as Data_Access_Event_Count and Data_Access_Sourcetype_User_Count respectively. Those searches are

index=_internal sourcetype IN(scheduler, splunkd_ui_access) | stats count
index=_internal sourcetype IN(scheduler,splunkd_ui_access) | stats count by sourcetype user

Several ways to save searches but ultimately you will want to do a few things. 
  1. Change permissions on the searches so that only the right folks should be able to see/edit them (don't keep them as Private)
  2. Adjust the query so that the items within the parentheses contain some variable name that is wrapped with dollar signs. I'm boring so I literally used "variable" (ie sourcetype IN($variable$) )
  3. Make sure the query is setup to run as the owner and not the user. From the nav bar in the top right go to Settings > Searches, reports, and alerts > 'Edit' on the search of interest > Edit Permissions > in here you want to select Run as User vs run as Owner

 You can also go into the advanced edit option > dispatchAs > and change that to user if you wanted.

Step 5 - add your saved searches to your dashboard

So navigate to the dashboard you created in step 3 and here we will add panels for the saved searches. Now in a slightly ironic twist instead of linking directly to the report we are going to call them through an inline search. We also need to pass the output from the search we created in step 3 to these searches. We haven't done this yet but we are going to tie that output to a token to pass into these searches. For demonstration purposes I'm going to set my token to be "token-sourcetype". Recall that in Step 4.2 I created a token reference called "variable". So with all of this information the queries I'm going to create panels around look like the following

| savedsearch Data_Access_Event_Count variable="$token-sourcetype$"

| savedsearch Data_Access_Sourcetype_User_Count variable="$token-sourcetype$"

When you create these panels they will give you an error. Don't worry that will be fixed.

It is important to put double quotes around the token you are passing into the saved search - in my case "$token-sourcetype$". If you don't, Splunk will essentially stop reading your results at the comma and process only the first value in your results.

Step 6 - update the XML related to the user query from step 3

So we have our dashboard with three panels on the dashboard that might look something like this






Now we need to have that first panel set the "token-sourcetype" token and hide the panel. We will set the token from the sourcetype field from the results of the query in this panel. That bit looks like this in the XML view

<set token="token-sourcetype">$result.sourcetype$</set>

and the panel can be hidden by adding a 'depends' to the table for a token that will never get a value.

<table depends="$hide_me$">

So going from 

....
      <table>
        <search>
          <query>| rest splunk_server=local /services/authentication/current-context | stats values(roles) as roles | mvexpand roles | search roles=group* | lookup log_access.csv role as roles | stats values(sourcetype) as sourcetype delim="," | nomv sourcetype</query>
.....

to

....
      <table depends="$hide_me$">
        <search>
          <done>
            <set token="token-sourcetype">$result.sourcetype$</set>
          </done>
          <query>| rest splunk_server=local /services/authentication/current-context | stats values(roles) as roles | mvexpand roles | search roles=group* | lookup log_access.csv role as roles | stats values(sourcetype) as sourcetype delim="," | nomv sourcetype</query>
....

Now our dashboard (with both roles) looks something like this. Note that I'm just looking at my internal logs and I've added some coloring in the second panel to highlight the different sourcetypes


Step 7 - test test test

As with anything you will want to test what you've created. There are several test options to include adding a panel that maybe shows the actual results of the search that identifies what roles the viewing user has as you work through "is the right data being displayed for the individual groups". You can also, while logged into the admin account, adjust that hidden search to show results of different groups; I referenced this a bit earlier. Basically you adjust which group(s) the search should target.

| rest splunk_server=local /services/authentication/current-context | stats values(roles) as roles | mvexpand roles | search roles=group* | lookup log_access.csv role as roles | stats values(sourcetype) as sourcetype delim="," | nomv sourcetype

What is nice (at least to me who is easily amused) is you can edit the search in the UI edit mode vs having to dive into the XML view. Of course you will also want to actually log in as a test account with the various roles to make sure they can get to the dashboard and things otherwise look good.

While that seems like a lot of steps and making sure you keep track of what you call things the approach is generally pretty straight forward. At this point you might want to add things like a custom time range picker. If you want add different filters for the users like a dropdown remember you will need to accommodate them in your saved search and the panel searches via tokens (steps 4, 5, & 6)

Hopefully this was helpful! Are there other ways or approaches you've used to accomplish the same thing? Would love to hear about them.

I'll go ahead and paste my full dashboard XML and highlight some pertinent parts.

<dashboard>
  <label>Data Access Test</label>
  <row>
    <panel>
      <title>hide me</title>
      <table depends="$hide_me$">
        <search>
          <done>
            <set token="token-sourcetype">$result.sourcetype$</set>
          </done>
          <query>| rest splunk_server=local /services/authentication/current-context | stats values(roles) as roles | mvexpand roles | search roles=group* | lookup log_access.csv role as roles | stats values(sourcetype) as sourcetype delim="," | nomv sourcetype</query>
          <earliest>-24h@h</earliest>
          <latest>now</latest>
          <sampleRatio>1</sampleRatio>
        </search>
        <option name="count">100</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>
  <row>
    <panel>
      <title>Event Counts</title>
      <single>
        <search>
          <query>| savedsearch Data_Access_Event_Count variable="$token-sourcetype$"</query>
          <earliest>$field1.earliest$</earliest>
          <latest>$field1.latest$</latest>
        </search>
        <option name="drilldown">none</option>
        <option name="refresh.display">progressbar</option>
      </single>
    </panel>
  </row>
  <row>
    <panel>
      <title>Event Counts by Sourcetype &amp; User</title>
      <table>
        <search>
          <query>| savedsearch Data_Access_Sourcetype_User_Count variable="$token-sourcetype$"</query>
          <earliest>$field1.earliest$</earliest>
          <latest>$field1.latest$</latest>
        </search>
        <option name="drilldown">none</option>
        <format type="color" field="sourcetype">
          <colorPalette type="sharedList"></colorPalette>
          <scale type="sharedCategory"></scale>
        </format>
      </table>
    </panel>
  </row>
</dashboard>

No comments:

Post a Comment