Security

Radius authentication/authorisation

huaraz
Explorer

Hi,

Is there an order in which userLogin, getUserInfo and getUsers is called ? I don't want to create a manual user mapping but use radius AV pairs returned during the user authentication. If I know userLogin is always first I could dynamically create a role mapping database and use it for later getUserInfo calls.

Is getUsers a requirement or optional ? If I use Radius I won't have the information of all possible users, but I could use the info of previously logged in users from my dynamically created role mapping database.

Would that work ?

Thank you
Markus

0 Karma

huaraz
Explorer

I created the below python script.

Can someone comment if that would be sufficient ?

Markus

"""
    This is only a sample authentication script, and is NOT SUPPORTED by Splunk.

    For the most basic example of how to use scripted auth, please see dumbScripted.py
    This script serves as an example of how to interact between splunkd and RADIUS.

    The example uses the RADIUS client from the freeradius server.
    http://www.freeradius.org/

    You must download and install this client for this script to function
    properly. If you already have your own RADIUS client you may use that
    but you may have to edit the function contactRADIUS to make it compatible
    with your client.

    Function breakdown
    Required:
        userLogin   : will be called on userLogin into splunk.
        getUserInfo : Get information about a particular user. ( username, realname, roles )
        getUsers    : Get a list of all users available.

    Optional Calls  :
        getSearchFilter : return a search filter for a given user. Called when the user searches.

"""
import sys, subprocess, getopt
import re, pickle, fcntl
from datetime import *

# keys we'll be using when talking with splunk.
USERNAME    = "username"
USERTYPE    = "role"
SUCCESS     = "--status=success"
FAILED      = "--status=fail"

# freeradius-client-1.1.6-40.1
RADIUS_CLIENT = '/opt/splunk/sbin/radiusclient'

RADIUS_USER   = 'User-Name'
RADIUS_PASS   = 'Password'
USERMAP_FILE  = '/opt/splunk/etc/usermap.pkl'
USERMAP_LOCK_FILE  = '/opt/splunk/etc/.usermap.pkl.lock'

roleMappingDict = {}

'''
This is a basic user database for storing users and their corresponding Splunk roles.

This is only intended to be a sample and is NOT SUPPORTED. If you only have a handful of users
this may suffice for mapping your users to roles. It may not scale well to thousands of users.

IMPORTANT: If you intend to use both the getUsersRole and getAllUsers functions defined here,
the roleMappingDict must have an entry for each user in your auth system. Otherwise, you could potentially
get roles for a particular user in getUsersRole that is not returned in getAllUsers (since we
default to returning the user role in getUsersRole). An incomplete database of users here would result in
undefined behavior.
'''

# read the inputs coming in and put them in a dict for processing.
def readInputs():
   optlist, args = getopt.getopt(sys.stdin.readlines(), '', ['username=', 'password='])

   returnDict = {}
   for name, value in optlist:
      returnDict[name[2:]] = value.strip()

   return returnDict

# Read dictionary from file
def readMapDict():
    global roleMappingDict
    try:
       userMap = open(USERMAP_FILE, 'rb')
    except IOError:
       return
    try:
       roleMappingDict = pickle.load(userMap)
    except EOFError:
       pass
    userMap.close()
    return

# If you want a user to have admin or power level you will need to add them
# to this map OR just replace getUserRole and getUserFilter function with
# your own code that restrieves this information from elsewhere.
# roleMappingDict = {
#     #username     #splunk role           # search filter                                 lastlogin
#     'boo'     :  ([ "admin" ],           [ 'NOT APACHE', 'NOT FLUBBER', 'NOT FLUBBER' ], [ date ]),
#     'root'    :  ([ "admin", "power" ],  [], [ date ]),
#     'peon'    :  ([ "user" ],            [], [ date ]),
#     'steve'   :  ([ "user" ],            [ 'NOT GLOBAL' ], [ date ]),
#     'john'    :  ([ "power" ],           [], [ date ] ),
#     'jack'    :  ([ "admin" ],           [], [ date ] )
# }

def getUsersRole( username ):
    global roleMappingDict
    readMapDict()
    if roleMappingDict.has_key( username ):
        return roleMappingDict[username][0]
    else:
        print "Unable to find user " + username
        print "Returning lowest role of user"
        return [ "user" ]


def getUsersFilters( username ):
    global roleMappingDict
    readMapDict()
    if roleMappingDict.has_key( username ):
        return roleMappingDict[username][1]
    else:
        print "Unable to find user " + username
        print "Returning no search filter"
        return [ "" ]

def getAllUsers():
    global roleMappingDict
    readMapDict()
    out = ""
    for u, r in roleMappingDict.iteritems():
        out += ' --userInfo=' + u + ';' + u + ';' + u + ';' + ':'.join(r[0])

    return out

def getOctalStr(s):
    oct = ''
    for c in s:
        oct += '\%03o' % ord(c)
    return oct

'''
This function will be called when a user enters their credentials in the login page in UI.
    Input:
        --username=<user> --password=<pass> 
    Output:
        On Success:
            --status=success
        On Failuire:
            Anyhing but --status=success

    Splunk will print everything outputted to stdin if there is an error so you can add debugging info
    that will be printed in splunkd.log when the system is in DEBUG mode.
'''
def userLogin( infoIn  ):
    global roleMappingDict
    # Create the list of arguments to pass to Popen
#    command = [RADIUS_CLIENT] + [RADIUS_USER + '=' + getOctalStr(infoIn['username'])] + [RADIUS_PASS + '=' + getOctalStr(infoIn['password'])]  + ['\n']
    command = [RADIUS_CLIENT] + [RADIUS_USER + '=' + infoIn['username']] + [RADIUS_PASS + '=' + infoIn['password']] + ['\n']


    proc = subprocess.Popen( command,
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                           )

    output = proc.communicate( )

    retCode = proc.wait()

    if retCode != 0:
       print FAILED
       return

    splunkRoles = [ "user" ]

# Look in output for attribute called Class and 
# store value in variable splunkRole
    splitLines = output[0].split('\n')
    for line in splitLines:
      if "Class" in line:
         role = re.sub(r'.*=(.*)',r'\1',line).strip()
         splunkRoles.append(role[1:len(role)-1])
#
# File lock to avoid two logins updating mapping dictionary file
#
    userMapLock = open(USERMAP_LOCK_FILE, 'w')
    fcntl.lockf(userMapLock, fcntl.LOCK_EX)
#
# Check if file with mapping dictionary exists and 
# read saved mapping dictionary
#
    try:
       userMap = open(USERMAP_FILE, 'rb')
    except IOError:
# File does not exist = > create it
       userMap = open(USERMAP_FILE, 'wr+b')
    try:
       roleMappingDict = pickle.load(userMap)
    except EOFError:
# Empty file => continue
       pass    
    userMap.close()
#
# Cleanup user mapping file 
# Delete stale users after 30 days
#
    for user in roleMappingDict:
        lastLoginDelta = datetime.now()-roleMappingDict[user][2]
        if lastLoginDelta > timedelta(days=30):
            del roleMappingDict[user]
# Updat mapping dictionary with  new user
    roleMappingDict.update({ infoIn['username'] : ( splunkRoles , [], datetime.now()) })
# Write updated mapping dictionary to file
    userMap = open(USERMAP_FILE, 'wb')
    pickle.dump(roleMappingDict,userMap)
    userMap.close()
# remove lock
    fcntl.lockf(userMapLock, fcntl.LOCK_UN)
    userMapLock.close()
    print SUCCESS
    return

'''
    This function prints out the details of the userId supplied.
    Input :
        --username=<user>
    Output:
        --status=success --userInfo=<userId>;<username>;<realname>;<role>:<role>:<role>    Note roles delimited by :
'''
def getUserInfo( infoIn ):
    roleList = getUsersRole( infoIn['username'] )

    outStr = SUCCESS + " --userInfo=" + infoIn["username"] + ";" + infoIn["username"] + ";" + infoIn["username"] + ";"
    for roleItem in roleList:
        outStr = outStr + roleItem + ":"

    print outStr



'''
    This function gets all the users in the system that scripted auth will work for.
    Input :
        N/A
    Output :
        --status=success --userInfo=<userId>;<username>;<realname>;<role>:<role>:<role> --userInfo=<userId>;<username>;<realname>;<role>:<role>:<role>  ...
'''
def getUsers( infoIn ):
    print SUCCESS + getAllUsers()


'''
    Gets the search filter for a given user.
    You must have the flag scriptSearchFilters set to 1 on the config for this function to be used.
    Input :
        --username=<username>
    Output:
        --search_filter=<filter> --search_fil....
'''
def getSearchFilter( infoIn ):
    outStr = SUCCESS
    filters = getUsersFilters( infoIn['username'] )
    outStr = SUCCESS

    for i in filters:
        outStr = outStr + " --search_filter=" + str(i) 

    print outStr 


def contactRADIUS( inforIn, callname ):
    print "edit this function"


if __name__ == "__main__":
   callName = sys.argv[1]
   dictIn = readInputs()

   returnDict = {}
   if callName == "userLogin":
      userLogin( dictIn )
   elif callName == "getUsers":
      getUsers( dictIn )
   elif callName == "getUserInfo":
      getUserInfo( dictIn )
   elif callName == "getSearchFilter":
      getSearchFilter( dictIn )
   else:
      print "ERROR unknown function call: " + callName
0 Karma
Get Updates on the Splunk Community!

Introducing the 2024 SplunkTrust!

Hello, Splunk Community! We are beyond thrilled to announce our newest group of SplunkTrust members!  The ...

Introducing the 2024 Splunk MVPs!

We are excited to announce the 2024 cohort of the Splunk MVP program. Splunk MVPs are passionate members of ...

Splunk Custom Visualizations App End of Life

The Splunk Custom Visualizations apps End of Life for SimpleXML will reach end of support on Dec 21, 2024, ...