Example #1
0
 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 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')
Example #3
0
 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)
Example #4
0
    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)
Example #6
0
    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('/')
Example #8
0
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('/')
Example #9
0
        # 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')
Example #10
0
    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('/')
Example #12
0
    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)