def sso_error(self, **kw): ''' Called to tell user that SSO login worked, but no splunk user exists. ''' if not util.in_sso_mode(): raise cherrypy.HTTPError(404) return self.render_template('account/sso_error.html')
def validate_ip(fn, self, *a, **kw): if util.in_sso_mode() and verify_sso: incoming_request_ip = cherrypy.request.remote.ip splunkweb_trusted_ip = splunk.util.stringToFieldList(cherrypy.config.get(SPLUNKWEB_TRUSTED_IP_CFG)) sso_mode = cherrypy.request.config.get(SPLUNKWEB_SSO_MODE_CFG, 'strict') current_remote_user = cherrypy.session.get(REMOTE_USER_SESSION_KEY) if incoming_request_ip not in splunkweb_trusted_ip: if current_remote_user: logger.warn('There was a user logged by SSO and somehow the splunkweb trustedIP is no longer valid. Removing the logged in user.') clean_session() if sso_mode and sso_mode.lower() == 'strict': raise cherrypy.HTTPError(403, _("Forbidden: Strict SSO Mode")) return fn(self, *a, **kw)
def validate_ip(fn, self, *a, **kw): if util.in_sso_mode() and verify_sso: incoming_request_ip = cherrypy.request.remote.ip splunkweb_trusted_ip = splunk.util.stringToFieldList( cherrypy.config.get(SPLUNKWEB_TRUSTED_IP_CFG)) sso_mode = cherrypy.request.config.get(SPLUNKWEB_SSO_MODE_CFG, 'strict') current_remote_user = cherrypy.session.get(REMOTE_USER_SESSION_KEY) if incoming_request_ip not in splunkweb_trusted_ip: if current_remote_user: logger.warn( 'There was a user logged by SSO and somehow the splunkweb trustedIP is no longer valid. Removing the logged in user.' ) clean_session() if sso_mode and sso_mode.lower() == 'strict': raise cherrypy.HTTPError(403, _("Forbidden: Strict SSO Mode")) return fn(self, *a, **kw)
def sso(self): import socket enabled = _("No") proxy_to_cherrypy = _('SSO is not enabled.') if util.in_sso_mode(): enabled = _("Yes") if cherrypy.request.remote.ip not in splunk.util.stringToFieldList(cherrypy.config.get('trustedIP')): proxy_to_cherrypy = _("No. SSO will not be used to authenticate this request.") else: proxy_to_cherrypy = _("Yes. SSO will be used to authenticate this request.") remote_user_header_name = cherrypy.request.config.get('remoteUser') or decorators.DEFAULT_REMOTE_USER_HEADER remote_user = cherrypy.request.headers.get(remote_user_header_name) if remote_user == None: remote_user = _("Not set. SSO may not be enabled or you may not be accessing Splunk via your proxy server.") server_info = splunk.entity.getEntity('/server', 'info', namespace=None, owner='anon') sso_mode = cherrypy.request.config.get(decorators.SPLUNKWEB_SSO_MODE_CFG) output = { 'host_name': socket.gethostname(), 'host_ip': socket.gethostbyname(socket.gethostname()), 'port': cherrypy.config.get('httpport'), 'sso_enabled': enabled, 'splunkweb_trusted_ip': cherrypy.config.get('trustedIP') or _('Not set. To enable try configuring the trustedIP setting in web.conf.'), 'splunkd_trusted_ip': cherrypy.config.get('splunkdTrustedIP') or _('Not set. To enable try configuring the trustedIP setting in server.conf.'), 'splunkweb_trusted_ip_matches': proxy_to_cherrypy, 'splunkweb_remote_ip': cherrypy.request.remote.ip, 'remote_user': remote_user, 'remote_user_header_name': remote_user_header_name, 'headers': cherrypy.request.headers, 'sso_mode': sso_mode } return self.render_template('debug/sso.html', output)
# Start with a clean and minty fresh session cherrypy.session.regenerate() cherrypy.session['sessionKey'] = sessionKey # TODO: get rest of user info cherrypy.session['user'] = { 'name': username, 'fullName': fullName, 'id': -1 } # Stash the remote user if splunkd is in SSO mode. Note we do not stash the user if the # incoming request IP does not match any of the IP addresses in the list of trusted IPs. # This allows users to still login via SSO, logout, and login as another user # but ensures that if they logout of SSO, they will be logged out of Splunk. if util.in_sso_mode(): incoming_request_ip = cherrypy.request.remote.ip splunkweb_trusted_ip = splunk.util.stringToFieldList(cherrypy.config.get(decorators.SPLUNKWEB_TRUSTED_IP_CFG)) if incoming_request_ip in splunkweb_trusted_ip: remote_user_header = cherrypy.request.config.get(decorators.SPLUNKWEB_REMOTE_USER_CFG) or decorators.DEFAULT_REMOTE_USER_HEADER cherrypy.session[decorators.REMOTE_USER_SESSION_KEY] = cherrypy.request.headers.get(remote_user_header) # Check for an expired license and override any action if one is present if cherrypy.config['license_state'] == 'EXPIRED': return self.redirect_to_url('/licensing') if return_to: return self.redirect_to_url(util.make_url_internal(return_to), translate=False) return self.redirect_to_url('/')
class AccountController(BaseController): """ Handle logging in and logging out """ # define filepath for successful login flag FLAG_FILE = util.make_splunkhome_path(['etc', '.ui_login']) @expose_page(methods='GET') def index(self): return self.redirect_to_url('/') def updateCookieUID(self): """ Creates and sets a long-lived cookie uid. If a uid cookie already exists it will not overwrite it. """ if cherrypy.request.cookie.get('uid') is None: cherrypy.response.cookie['uid'] = splunk.util.uuid4().upper( ) # for consistency as splunkd returns uppercase guid cherrypy.response.cookie['uid']['expires'] = 5 * 365 * 24 * 3600 def genCookieTest(self): """ Creates a random cval integer """ return random.randint(0, 2**31) def cookieTest(self, cval): """ tests the given string and cval cookie value for type and value equality """ try: return int(cherrypy.request.cookie['cval'].value) == int(cval) except: return False def updateCookieTest(self): """ set a cookie to check that cookies are enabled and pass the value to the form """ cval = cherrypy.request.cookie.get('cval') if cval: try: cval = int(cval.value) except: cval = self.genCookieTest() else: cval = self.genCookieTest() cherrypy.response.cookie['cval'] = cval return cval def handleStormLogin(self, **kwargs): from splunk.appserver.mrsparkle.lib import storm cherrypy.session.regenerate() if cherrypy.request.method == 'POST' and kwargs.has_key('storm_token'): ts, token = storm.decrypt_token(kwargs['storm_token']) max_token_age = cherrypy.config.get('storm_max_token_age', 3600) if ts + max_token_age < time.time(): logger.warn("Storm session token has expired") token = defaults = None else: logger.info("Got storm token OK") cherrypy.session['storm_token'] = token new_session = True attempts = 2 ok = False while attempts: attempts -= 1 defaults = storm.get_storm_defaults(new_session) if not defaults: continue if splunk.auth.ping(sessionKey=defaults['sessionKey']): ok = True break if not defaults or not ok: if not defaults: logger.error( "Failed to fetch user's default settings from Storm" ) else: logger.error( "Storm issued a token with an invalid session key %s" % defaults['sessionKey']) token = defaults = None cherrypy.session['storm_token'] = None else: defaults = storm.get_storm_defaults() if not defaults: url = cherrypy.config.get('storm_user_url') if not url: storm_host = cherrypy.config.get('storm_host', '127.0.0.1') storm_port = cherrypy.config.get('storm_port', 80) if cherrypy.config['storm_port'] != 80: url = "http://%s:%s/" % (storm_host, storm_port) else: url = "http://%s/" % (storm_host) logger.warn( "No storm defaults found - Redirecting user back to storm appserver storm_token_set=%s" % kwargs.has_key('storm_token')) raise cherrypy.HTTPRedirect(url) cherrypy.session['user'] = { 'name': defaults['user'], 'fullName': 'Storm User', 'id': 1 } cherrypy.session['sessionKey'] = defaults['sessionKey'] return self.redirect_to_url('/') def getUpdateCheckerBaseURL(self): # validate the checker URI updateCheckerBaseURL = str( cherrypy.config.get('updateCheckerBaseURL', '')).strip() if not any( map(updateCheckerBaseURL.startswith, ['http://', 'https://'])): updateCheckerBaseURL = None return updateCheckerBaseURL def getLoginTemplateArgs(self, return_to=None, cval=None, session_expired_pw_change=False): """Generate the base template arguments for account/login.html""" ref = cherrypy.request.headers.get('Referer') # free license doesn't really expire; we just push the nagware here if cherrypy.config.get('is_free_license'): session_expired = False else: session_expired = ref and ref.startswith( cherrypy.request.base) and not ref.endswith( cherrypy.request.path_info) templateArgs = { 'return_to': return_to, 'session_expired': session_expired, 'session_expired_pw_change': session_expired_pw_change, 'updateCheckerBaseURL': self.getUpdateCheckerBaseURL(), 'serverInfo': self.getServerInfo(), 'isAutoComplete': self.isAutoComplete(), 'bad_cookies': False, 'cval': cval or self.updateCookieTest(), 'loginContent': cherrypy.config.get('login_content', ''), 'hasLoggedIn': True } return templateArgs @expose_page(must_login=False, methods=['GET', 'POST'], verify_session=False) @lock_session @set_cache_level('never') def login(self, username=None, password=None, return_to=None, cval=None, **kwargs): # Force a refresh of startup info so that we know to # redirect if license stuff has expired. startup.initVersionInfo(force=True) updateCheckerBaseURL = self.getUpdateCheckerBaseURL() # set a long lived uid cookie self.updateCookieUID() templateArgs = self.getLoginTemplateArgs(return_to=return_to, cval=cval) # Storm if cherrypy.config.get('storm_enabled'): return self.handleStormLogin(**kwargs) # # GET # if cherrypy.request.method == 'GET': # free license will auth on anything so statically seed if cherrypy.config.get('is_free_license'): # Start with a clean and minty fresh session cherrypy.session.regenerate() cherrypy.session['user'] = { 'name': 'admin', 'fullName': 'Administrator', 'id': 1 } sessionKey = splunk.auth.getSessionKey( "admin", "freeneedsnopassword", hostPath=self.splunkd_urlhost) cherrypy.session['sessionKey'] = sessionKey if not updateCheckerBaseURL: return self.redirect_to_url('/app/%s' % splunk.getDefault('namespace')) # check for previously successful login templateArgs['hasLoggedIn'] = self.hasLoggedIn() # otherwise, show page return self.render_template('account/login.html', templateArgs) # # POST # # Check that the cookie we set when the login page was loaded has made it to us intact if 'cval' not in cherrypy.request.cookie or not self.cookieTest(cval): templateArgs['bad_cookies'] = 1 templateArgs['cval'] = self.updateCookieTest() return self.render_template('account/login.html', templateArgs) try: sessionKey = splunk.auth.getSessionKey( username, password, hostPath=self.splunkd_urlhost) except splunk.AuthenticationFailed, e: templateArgs['invalid_password'] = 1 templateArgs['cval'] = self.updateCookieTest() return self.render_template('account/login.html', templateArgs) en = splunk.entity.getEntity('authentication/users', username, sessionKey=sessionKey) fullName = username if en and 'realname' in en and en['realname']: fullName = en['realname'] # Start with a clean and minty fresh session cherrypy.session.regenerate() cherrypy.session['sessionKey'] = sessionKey # TODO: get rest of user info cherrypy.session['user'] = { 'name': username, 'fullName': fullName, 'id': -1 } # Log user login ua = cherrypy.request.headers.get('user-agent', 'unknown') ip = cherrypy.request.remote.ip logger.info('user=%s action=login status=success session=%s ' \ 'reason=user-initiated useragent="%s" clientip=%s' % (username, sessionKey, ua, ip)) # Stash the remote user if splunkd is in SSO mode. Note we do not stash the user if the # incoming request IP does not match any of the IP addresses in the list of trusted IPs. # This allows users to still login via SSO, logout, and login as another user # but ensures that if they logout of SSO, they will be logged out of Splunk. if util.in_sso_mode(): incoming_request_ip = cherrypy.request.remote.ip splunkweb_trusted_ip = splunk.util.stringToFieldList( cherrypy.config.get(decorators.SPLUNKWEB_TRUSTED_IP_CFG)) if incoming_request_ip in splunkweb_trusted_ip: remote_user_header = cherrypy.request.config.get( decorators.SPLUNKWEB_REMOTE_USER_CFG ) or decorators.DEFAULT_REMOTE_USER_HEADER cherrypy.session[ decorators. REMOTE_USER_SESSION_KEY] = cherrypy.request.headers.get( remote_user_header) # Check for an expired license and override any action if one is present if cherrypy.config['license_state'] == 'EXPIRED': templateArgs['return_to'] = '/licensing' # If this is the first time admin has logged in, suggest changing the password if not self.hasLoggedIn() and username == 'admin': self.setLoginFlag(True) templateArgs = {} templateArgs['return_to'] = return_to templateArgs['cpSessionKey'] = cherrypy.session.id return self.render_template('account/passwordchange.html', templateArgs) if return_to and return_to[0] == '/': try: #collapse multiple slash into one, this should also prevent forward to external sites. return_to = '/' + return_to.lstrip('/') return self.redirect_to_url(return_to, translate=False) except util.InvalidURLException: # invalid character in the URL supplied; fall through and redirect to / instead logger.warn("Invalid return_to URL passed to login page") pass return self.redirect_to_url('/')
# Start with a clean and minty fresh session cherrypy.session.regenerate() cherrypy.session['sessionKey'] = sessionKey # TODO: get rest of user info cherrypy.session['user'] = { 'name': username, 'fullName': fullName, 'id': -1 } # Stash the remote user if splunkd is in SSO mode. Note we do not stash the user if the # incoming request IP does not match any of the IP addresses in the list of trusted IPs. # This allows users to still login via SSO, logout, and login as another user # but ensures that if they logout of SSO, they will be logged out of Splunk. if util.in_sso_mode(): incoming_request_ip = cherrypy.request.remote.ip splunkweb_trusted_ip = splunk.util.stringToFieldList( cherrypy.config.get(decorators.SPLUNKWEB_TRUSTED_IP_CFG)) if incoming_request_ip in splunkweb_trusted_ip: remote_user_header = cherrypy.request.config.get( decorators.SPLUNKWEB_REMOTE_USER_CFG ) or decorators.DEFAULT_REMOTE_USER_HEADER cherrypy.session[ decorators. REMOTE_USER_SESSION_KEY] = cherrypy.request.headers.get( remote_user_header) # Check for an expired license and override any action if one is present if cherrypy.config['license_state'] == 'EXPIRED': return self.redirect_to_url('/licensing')
def preform_sso_check(fn, self, *a, **kw): ''' The core of SSO. This validates SSO users, ensures that users are logged out of Splunk when logged out of SSO, etc. ''' if util.in_sso_mode(): logger.debug('In SSO mode.') # Retrieve the name of the remote user header passed from the proxy remote_user_header = cherrypy.request.config.get( SPLUNKWEB_REMOTE_USER_CFG) or DEFAULT_REMOTE_USER_HEADER # Retrieve the incoming remote user incoming_remote_user = cherrypy.request.headers.get( remote_user_header) # Retrieve the last user who visited via SSO and was stored in the session current_remote_user = cherrypy.session.get(REMOTE_USER_SESSION_KEY) # Retrieve the splunkd session key splunkd_session_key = cherrypy.session.get(SPLUNKD_SESSION_KEY) # The IP address of the request, used to validate the incoming req. incoming_request_ip = cherrypy.request.remote.ip # The list of IP addresses that are trusted by splunkweb splunkweb_trusted_ip = splunk.util.stringToFieldList( cherrypy.config.get(SPLUNKWEB_TRUSTED_IP_CFG)) # We should only ever evaluate this condition if SSOMode is not strict but the IPs don't match. # In this case we allow the user to login via regular splunk auth. # Not happy that we do this check twice per request, but without this line users would be # authed into SSO even if originating from an invalid IP. if incoming_request_ip not in splunkweb_trusted_ip: logger.info( 'The incoming request did not originate from a trusted IP address, thus SSO will not be performed. However, the user will be able to login to Splunk manually. Splunkweb must have SSOMode set to permissive.' ) return fn(self, *a, **kw) logger.debug( 'Request comes from a trusted source, will validate %s header' % remote_user_header) # There should be 8 cases here (2^3 possible choices between the three options). # Some of these are redundant, but I've left them verbose so it's easier to understand # why certain cases are handled in specific ways. if incoming_remote_user: # Case 1: They don't have a session, or have an invalid session if not splunkd_session_key or not splunk.auth.ping( sessionKey=splunkd_session_key): logger.debug( 'SSO CASE 1: %s header is set but no valid splunkd session key could be found.' % remote_user_header) login(self, incoming_remote_user) # Case 2: They have a session, but they don't seem to match the previously logged in user. # Maybe they logged out of SSO, but not splunk? elif current_remote_user and incoming_remote_user != current_remote_user: logger.debug( 'SSO CASE 2: %s header is set, splunkd session key is available and valid, but there is a mismatch between the user stored in the session and the incoming user. %s != %s' % (remote_user_header, current_remote_user, incoming_remote_user)) login(self, incoming_remote_user) # Case 3: They have a session but there is no current logged in user. The only way this # could knowingly happen is if the user's session is destroyed somewhere and the current # SSO user is not placed back in the session before coming back here. Maybe this could happen # if the user access splunkweb on its direct port, not the proxy. First time login # should have been handled by case 1, but this is put here for redundancy. Also note, # this requires we stash the Remote-User in the session after a user logins # in account/login. Perhaps less than ideal but it's not obvious how else to guard # against a user logging into SSO, logging out of splunk, logging in to splunk as admin # then logging out of SSO, then another user comes along, logs into SSO, hits splunk and # because we never checked for current_remote_user == None (assuming this wasn't here) # they suddenly login as admin! elif current_remote_user == None: logger.warn( 'SSO CASE 3: %s header is set and a valid session key exists, but the visitor was strangely not previously stored in the session.' % remote_user_header) login(self, incoming_remote_user) # Case 4: The user has a valid session and matches the current_remote_user. They are authed # which is confirmed by the require_login decorator. logger.debug( 'SSO CASE 4: %s header is set, user in the session matches the user in the %s header and the user is authed.' % (remote_user_header, remote_user_header)) else: # Case 5: There was a user here and now they are gone. This is sketchy, maybe someone # got into the actual splunkweb server when they shouldn't have after logging into SSO. # Logout them out to be sure. if current_remote_user: logger.error( 'SSO CASE 5: %s header is NOT set, but a remote user was stored in the session before. Logged in user will be logged out.' % remote_user_header) clean_session() logger.debug( 'SSO CASE 5,6,7,8: %s header is NOT set, the user\'s fate will be determined by the validity of their session key.' % remote_user_header) # Case 6: The user has an invalid sessionKey, and no current_remote_user. In this case # we let the require_login handler take care of the invalid auth. # Case 7: The user has a valid session but did not provide an incoming_remote_user or # a current_remote_user. They are allowed through if the require_login decorator allows # them access. Unclear if this should be handled differently. Presumably someone could # want splunkweb access for super special users who can access the splunkweb server # directly even while in SSO mode. # Case 8: There is no incoming_remote_user and no current_remote_user and no session. # They will be punted by require_login. return fn(self, *a, **kw)
class AccountController(BaseController): """ Handle logging in and logging out """ # define filepath for successful login flag FLAG_FILE = util.make_splunkhome_path(['etc', '.ui_login']) # Store up to 100 credentials in memory during change password operations credential_cache = util.LRUDict(capacity=100) # The LRUDict is not thread safe; acquire a lock before operating with it credential_lock = threading.Lock() @expose_page(methods='GET') def index(self): return self.redirect_to_url('/') def updateCookieUID(self): """ Creates and sets a long-lived cookie uid. If a uid cookie already exists it will not overwrite it. """ if cherrypy.request.cookie.get('uid') is None: cherrypy.response.cookie['uid'] = splunk.util.uuid4().upper( ) # for consistency as splunkd returns uppercase guid cherrypy.response.cookie['uid']['expires'] = 5 * 365 * 24 * 3600 def genCookieTest(self): """ Creates a random cval integer """ return random.randint(0, 2**31) def cookieTest(self, cval): """ tests the given string and cval cookie value for type and value equality """ try: return int(cherrypy.request.cookie['cval'].value) == int(cval) except: return False def updateCookieTest(self): """ set a cookie to check that cookies are enabled and pass the value to the form """ cval = cherrypy.request.cookie.get('cval') if cval: try: cval = int(cval.value) except: cval = self.genCookieTest() else: cval = self.genCookieTest() cherrypy.response.cookie['cval'] = cval return cval def handleStormLogin(self, return_to=None, **kwargs): from splunk.appserver.mrsparkle.lib import storm cherrypy.session.regenerate() if cherrypy.request.method == 'POST' and kwargs.has_key('storm_token'): ts, token = storm.decrypt_token(kwargs['storm_token']) max_token_age = cherrypy.config.get('storm_max_token_age', 3600) if ts + max_token_age < time.time(): logger.warn("Storm session token has expired") token = defaults = None else: logger.info("Got storm token OK") cherrypy.session['storm_token'] = token new_session = True attempts = 2 ok = False while attempts: attempts -= 1 defaults = storm.get_storm_defaults(new_session) if not defaults: continue if splunk.auth.ping(sessionKey=defaults['sessionKey']): ok = True break if not defaults or not ok: if not defaults: logger.error( "Failed to fetch user's default settings from Storm" ) else: logger.error( "Storm issued a token with an invalid session key %s" % defaults['sessionKey']) token = defaults = None cherrypy.session['storm_token'] = None else: defaults = storm.get_storm_defaults() if not defaults: url = cherrypy.config.get('storm_user_url') if not url: storm_host = cherrypy.config.get('storm_host', '127.0.0.1') storm_port = cherrypy.config.get('storm_port', 80) if cherrypy.config['storm_port'] != 80: url = "http://%s:%s/" % (storm_host, storm_port) else: url = "http://%s/" % (storm_host) if return_to: return_quoted = urllib.quote_plus(return_to) url += "?return_to_splunkweb=" + return_quoted logger.warn( "action=storm_login_failed, redirect_url=%s, " "storm_token_set=%s", url, kwargs.has_key('storm_token')) raise cherrypy.HTTPRedirect(url) cherrypy.session['user'] = { 'name': defaults['user'], 'fullName': 'Storm User', 'id': 1 } cherrypy.session['sessionKey'] = defaults['sessionKey'] if return_to: # return_to could potentially have a query string and a fragment, and hence break in IE6 # since we're bypassing self.redirect_to_url, we have to check for that if util.is_ie_6( ) and not util.redirect_url_is_ie_6_safe(return_to): return self.client_side_redirect( util.make_url_internal(return_to)) raise cherrypy.HTTPRedirect(util.make_url_internal(return_to)) else: return self.redirect_to_url('/') def getUpdateCheckerBaseURL(self): # validate the checker URI updateCheckerBaseURL = str( cherrypy.config.get('updateCheckerBaseURL', '')).strip() if not any( map(updateCheckerBaseURL.startswith, ['http://', 'https://'])): updateCheckerBaseURL = None return updateCheckerBaseURL def getLoginTemplateArgs(self, return_to=None, session_expired_pw_change=False): """Generate the base template arguments for account/login.html""" ref = cherrypy.request.headers.get('Referer') # free license doesn't really expire; we just push the nagware here if cherrypy.config.get('is_free_license'): session_expired = False else: session_expired = ref and ref.startswith( cherrypy.request.base) and not ref.endswith( cherrypy.request.path_info) templateArgs = { 'return_to': return_to, 'session_expired': session_expired, 'session_expired_pw_change': session_expired_pw_change, 'updateCheckerBaseURL': self.getUpdateCheckerBaseURL(), 'serverInfo': self.getServerInfo(), 'isAutoComplete': self.isAutoComplete(), 'bad_cookies': False, 'cval': self.updateCookieTest(), 'loginContent': cherrypy.config.get('login_content', ''), 'hasLoggedIn': True } return templateArgs @expose_page(must_login=False, methods=['GET', 'POST'], verify_session=False) @lock_session @set_cache_level('never') def login(self, username=None, password=None, return_to=None, cval=None, newpassword=None, **kwargs): # Force a refresh of startup info so that we know to # redirect if license stuff has expired. startup.initVersionInfo(force=True) updateCheckerBaseURL = self.getUpdateCheckerBaseURL() # set a long lived uid cookie self.updateCookieUID() templateArgs = self.getLoginTemplateArgs(return_to=return_to) if not return_to: return_to = '/' if return_to[0] != '/': return_to = '/' + return_to #dont allow new login if session established. if cherrypy.session.get('sessionKey') and return_to: raise cherrypy.HTTPRedirect(util.make_url_internal(return_to)) # Storm if cherrypy.config.get('storm_enabled'): return self.handleStormLogin(return_to=return_to, **kwargs) # # GET # if cherrypy.request.method == 'GET' and newpassword is None: # free license will auth on anything so statically seed if cherrypy.config.get('is_free_license'): # Start with a clean and minty fresh session cherrypy.session.regenerate() cherrypy.session['user'] = { 'name': 'admin', 'fullName': 'Administrator', 'id': 1 } sessionKey = splunk.auth.getSessionKey( "admin", "freeneedsnopassword", hostPath=self.splunkd_urlhost) cherrypy.session['sessionKey'] = sessionKey if not updateCheckerBaseURL: return self.redirect_to_url('/app/%s' % splunk.getDefault('namespace')) # check for previously successful login templateArgs['hasLoggedIn'] = self.hasLoggedIn() if templateArgs['return_to'] is None and cherrypy.config.get( 'root_endpoint') not in ['/', None, '']: templateArgs['return_to'] = util.make_url_internal( cherrypy.config.get('root_endpoint')) # otherwise, show page return self.render_template('account/login.html', templateArgs) # # POST # # Check that the cookie we set when the login page was loaded has made it to us intact if 'cval' not in cherrypy.request.cookie or not self.cookieTest(cval): templateArgs['bad_cookies'] = 1 return self.render_template('account/login.html', templateArgs) ua = cherrypy.request.headers.get('user-agent', 'unknown') ip = cherrypy.request.remote.ip if username: username = username.strip().lower() try: sessionKey = splunk.auth.getSessionKey( username, password, hostPath=self.splunkd_urlhost, newPassword=newpassword) except splunk.AuthenticationFailed, e: logger.error('user=%s action=login status=failure ' \ 'reason=user-initiated useragent="%s" clientip=%s ERROR=%s' % (username, ua, ip, str(e.msg))) templateArgs['invalid_password'] = 1 forced_password_change = str(e.msg).count('fpc') forced_password_message = str(e.extendedMessages) if forced_password_change: templateArgs['fpc'] = True # cache current credentials in memory only credentials = {'username': username, 'password': password} with AccountController.credential_lock: AccountController.credential_cache[ cherrypy.session.id] = credentials cherrypy.session['cval'] = cval cherrypy.session['fpc'] = True # forced password change templateArgs['err'] = _(forced_password_message) logger.info('user=%s action=login status=%s' % (username, forced_password_message)) return self.render_template('account/passwordchange.html', templateArgs) else: return self.render_template('account/login.html', templateArgs) en = splunk.entity.getEntity('authentication/users', username, sessionKey=sessionKey) fullName = username if en and 'realname' in en and en['realname']: fullName = en['realname'] # Start with a clean and minty fresh session cherrypy.session.regenerate() cherrypy.session['sessionKey'] = sessionKey # TODO: get rest of user info cherrypy.session['user'] = { 'name': username, 'fullName': fullName, 'id': -1 } # Log user login logger.info('user=%s action=login status=success session=%s ' \ 'reason=user-initiated useragent="%s" clientip=%s' % (username, sessionKey, ua, ip)) # Stash the remote user if splunkd is in SSO mode. Note we do not stash the user if the # incoming request IP does not match any of the IP addresses in the list of trusted IPs. # This allows users to still login via SSO, logout, and login as another user # but ensures that if they logout of SSO, they will be logged out of Splunk. if util.in_sso_mode(): incoming_request_ip = cherrypy.request.remote.ip splunkweb_trusted_ip = splunk.util.stringToFieldList( cherrypy.config.get(decorators.SPLUNKWEB_TRUSTED_IP_CFG)) if incoming_request_ip in splunkweb_trusted_ip: remote_user_header = cherrypy.request.config.get( decorators.SPLUNKWEB_REMOTE_USER_CFG ) or decorators.DEFAULT_REMOTE_USER_HEADER cherrypy.session[ decorators. REMOTE_USER_SESSION_KEY] = cherrypy.request.headers.get( remote_user_header) # Check for an expired license and override any action if one is present if cherrypy.config['license_state'] == 'EXPIRED': templateArgs['return_to'] = '/licensing' # If this is the first time admin has logged in, suggest changing the password if not self.hasLoggedIn() and username == 'admin': self.setLoginFlag(True) templateArgs = {} templateArgs['return_to'] = return_to templateArgs['cpSessionKey'] = cherrypy.session.id return self.render_template('account/passwordchange.html', templateArgs) if return_to: # return_to could potentially have a query string and a fragment, and hence break in IE6 # since we're bypassing self.redirect_to_url, we have to check for that if util.is_ie_6( ) and not util.redirect_url_is_ie_6_safe(return_to): return self.client_side_redirect( util.make_url_internal(return_to)) # We need to redirect to the return_to page, but we also need to return # the new CSRF cookie. We do this by creating the redirect but not # raising it as an exception. Instead, we use set_response (which # you can read about here: http://docs.cherrypy.org/dev/refman/_cperror.html#functions), # which will set it on the cherrypy.response object. # Finally, we also do not return any content, since there is none # to return (as it is a redirect). redirect_response = cherrypy.HTTPRedirect( util.make_url_internal(return_to)) redirect_response.set_response() util.setFormKeyCookie() return return self.redirect_to_url('/')
def preform_sso_check(fn, self, *a, **kw): ''' The core of SSO. This validates SSO users, ensures that users are logged out of Splunk when logged out of SSO, etc. ''' if util.in_sso_mode(): logger.debug('In SSO mode.') # Retrieve the name of the remote user header passed from the proxy remote_user_header = cherrypy.request.config.get(SPLUNKWEB_REMOTE_USER_CFG) or DEFAULT_REMOTE_USER_HEADER # Retrieve the incoming remote user incoming_remote_user = cherrypy.request.headers.get(remote_user_header) # Retrieve the last user who visited via SSO and was stored in the session current_remote_user = cherrypy.session.get(REMOTE_USER_SESSION_KEY) # Retrieve the splunkd session key splunkd_session_key = cherrypy.session.get(SPLUNKD_SESSION_KEY) # The IP address of the request, used to validate the incoming req. incoming_request_ip = cherrypy.request.remote.ip # The list of IP addresses that are trusted by splunkweb splunkweb_trusted_ip = splunk.util.stringToFieldList(cherrypy.config.get(SPLUNKWEB_TRUSTED_IP_CFG)) # We should only ever evaluate this condition if SSOMode is not strict but the IPs don't match. # In this case we allow the user to login via regular splunk auth. # Not happy that we do this check twice per request, but without this line users would be # authed into SSO even if originating from an invalid IP. if incoming_request_ip not in splunkweb_trusted_ip: logger.info('The incoming request did not originate from a trusted IP address, thus SSO will not be performed. However, the user will be able to login to Splunk manually. Splunkweb must have SSOMode set to permissive.') return fn(self, *a, **kw) logger.debug('Request comes from a trusted source, will validate %s header' % remote_user_header) # There should be 8 cases here (2^3 possible choices between the three options). # Some of these are redundant, but I've left them verbose so it's easier to understand # why certain cases are handled in specific ways. if incoming_remote_user: # Case 1: They don't have a session, or have an invalid session if not splunkd_session_key or not splunk.auth.ping(sessionKey=splunkd_session_key): logger.debug('SSO CASE 1: %s header is set but no valid splunkd session key could be found.' % remote_user_header) login(self, incoming_remote_user) # Case 2: They have a session, but they don't seem to match the previously logged in user. # Maybe they logged out of SSO, but not splunk? elif current_remote_user and incoming_remote_user != current_remote_user: logger.debug('SSO CASE 2: %s header is set, splunkd session key is available and valid, but there is a mismatch between the user stored in the session and the incoming user. %s != %s' % (remote_user_header, current_remote_user, incoming_remote_user)) login(self, incoming_remote_user) # Case 3: They have a session but there is no current logged in user. The only way this # could knowingly happen is if the user's session is destroyed somewhere and the current # SSO user is not placed back in the session before coming back here. Maybe this could happen # if the user access splunkweb on its direct port, not the proxy. First time login # should have been handled by case 1, but this is put here for redundancy. Also note, # this requires we stash the Remote-User in the session after a user logins # in account/login. Perhaps less than ideal but it's not obvious how else to guard # against a user logging into SSO, logging out of splunk, logging in to splunk as admin # then logging out of SSO, then another user comes along, logs into SSO, hits splunk and # because we never checked for current_remote_user == None (assuming this wasn't here) # they suddenly login as admin! elif current_remote_user == None: logger.warn('SSO CASE 3: %s header is set and a valid session key exists, but the visitor was strangely not previously stored in the session.' % remote_user_header) login(self, incoming_remote_user) # Case 4: The user has a valid session and matches the current_remote_user. They are authed # which is confirmed by the require_login decorator. logger.debug('SSO CASE 4: %s header is set, user in the session matches the user in the %s header and the user is authed.' % (remote_user_header, remote_user_header)) else: # Case 5: There was a user here and now they are gone. This is sketchy, maybe someone # got into the actual splunkweb server when they shouldn't have after logging into SSO. # Logout them out to be sure. if current_remote_user: logger.error('SSO CASE 5: %s header is NOT set, but a remote user was stored in the session before. Logged in user will be logged out.' % remote_user_header) clean_session() logger.debug('SSO CASE 5,6,7,8: %s header is NOT set, the user\'s fate will be determined by the validity of their session key.' % remote_user_header) # Case 6: The user has an invalid sessionKey, and no current_remote_user. In this case # we let the require_login handler take care of the invalid auth. # Case 7: The user has a valid session but did not provide an incoming_remote_user or # a current_remote_user. They are allowed through if the require_login decorator allows # them access. Unclear if this should be handled differently. Presumably someone could # want splunkweb access for super special users who can access the splunkweb server # directly even while in SSO mode. # Case 8: There is no incoming_remote_user and no current_remote_user and no session. # They will be punted by require_login. return fn(self, *a, **kw)