Example #1
0
 def handleRemove(self, confInfo):
     try:
         self.delete()
     except BaseException as ex:
         #for datamodel, throw an exception when remove fails.
         raise admin.InternalException('Failed to delete model: %s' %
                                       (self.callerArgs.id))
Example #2
0
    def handleCreate(self, confInfo):
        args = self.callerArgs.data

        # Sanity checking for app ID: no special chars and shorter than 100 chars
        appName = self.callerArgs.id
        if not appName or len(appName) == 0:
            raise admin.ArgValidationException(
                _('App folder name is not set.'))

        if re.search('[^A-Za-z0-9._-]', appName):
            raise admin.ArgValidationException(
                _('App folder name cannot contain spaces or special characters.'
                  ))

        if len(appName) > 100:
            raise admin.ArgValidationException(
                _('App folder name cannot be longer than 100 characters.'))

        kwargs = {
            'label': _getFieldValue(args, HTTP_POST_LABEL, appName,
                                    maxLen=100),
            'visible': _getFieldValue(args, HTTP_POST_VISIBLE, 'true'),
            'author': _getFieldValue(args, HTTP_POST_AUTHOR, '', maxLen=100),
            'description': _getFieldValue(args, HTTP_POST_DESC, '',
                                          maxLen=500),
            'configured': _getFieldValue(args, HTTP_POST_CONFIGURED, '0'),
        }
        template = _getFieldValue(args, HTTP_POST_TEMPLATE, DEFAULT_TEMPLATE)

        try:
            url = appbuilder.createApp(appName, template, **kwargs)
            appbuilder.addUploadAssets(appName)
        except splunk.RESTException, e:
            raise admin.InternalException(e.msg)
Example #3
0
    def handleCreate(self, confInfo):
        confInfo.addDeprecationMsg()
        location = self.callerArgs.id

        force = False
        if FORCE in self.callerArgs:
            force = bundle_paths.parse_boolean(self.callerArgs[FORCE][0])

        try:
            bundle, status = appbuilder.installApp(location, force)
        except splunk.RESTException as e:
            raise admin.InternalException(e.msg)

        upgraded = (status == bundle_paths.BundleInstaller.STATUS_UPGRADED)

        appName = bundle.name(raw=True) or ''
        confInfo[appName].append('name', appName)
        confInfo[appName].append('location', bundle.location() or '')
        confInfo[appName].append('status',
                                 'upgraded' if upgraded else 'installed')
        confInfo[appName].append('source_location', location)

        if not upgraded:
            reloader = 'apps/local/_reload'
        else:
            reloader = 'apps/local/%s/_reload' % urllib.quote(bundle.name())
        rest.simpleRequest(reloader, sessionKey=self.getSessionKey())
Example #4
0
    def simple_request_eai(self, url, action, method, params=None, get_args=None):
        """
        Returns the payload response from a simpleRequest call

        Arguments
        url -- The REST handler endpoint to use in the simpleRequest
        params -- The parameters sent in the POST body of the simpleRequest
        """
        if not params:
            params = {}

        default_get_args = dict(output_mode='json')
        if get_args:
            default_get_args.update(get_args)

        logger.info('%s request %s' % (method, url))
        try:
            response, content = rest.simpleRequest(url, getargs=default_get_args, postargs=params,
                                                   method=method, sessionKey=self.getSessionKey())
        except Exception as e:
            logger.error(e)
            raise admin.ServiceUnavailableException('Unable to %s %s entry.' % (action, url))
        try:
            payload = json.loads(content)
        except Exception as e:
            logger.error(e)
            raise admin.InternalException('Unable to parse %s response payload.' % url)
        if response.status not in [200, 201]:
            message = self.simple_request_messages_to_str(response.messages)
            logger.error(
                'handler_message="request failed, status code not in successful range" status="%s" params="%s" splunkd_message="%s"' % (
                response.status, params, message))
            raise admin.AlreadyExistsException(
                'Unable to %s %s entry. %s' % (action, url, message))
        return payload
Example #5
0
 def update(self,
            name="snow_account",
            snow_url=None,
            release="automatic",
            username=None,
            password=None):
     response, data = self.validate_snow_account(snow_url, release,
                                                 username, password)
     if response.status not in (200, 201):
         raise admin.InternalException(
             "Can not authenticate the ServiceNow account you provided.")
     account = self.get_by_name(name)
     props = {
         "url": snow_url,
         "release": release,
         "username": username,
         "password": password
     }
     account.update(
         **{
             "body": binding._encode(**props),
             "app": "Splunk_TA_snow",
             "owner": self._service.namespace.get('owner')
         })
     return self.get_by_name(name)
Example #6
0
    def handleCreate(self, confInfo):
        location = self.callerArgs.id

        force = False
        if FORCE in self.callerArgs:
            force = bundle_paths.parse_boolean(self.callerArgs[FORCE][0])

        try:
            bundle, status = appbuilder.installApp(location, force)
        except splunk.RESTException, e:
            raise admin.InternalException(e.msg)
Example #7
0
    def handleEdit(self, confInfo):
        logger.debug('handleEdit is called. -- action = %s, id = %s' %
                     (self.customAction, self.callerArgs.id))

        try:
            args = self.getCallerArgs()
            self.update(**args)
            self.handleList(confInfo)
        except BaseException as ex:
            #for datamodel, throw an exception when edit fails
            raise admin.InternalException('Failed to edit model: %s' %
                                          (self.callerArgs.id))
Example #8
0
    def validate_params(self, schema, params):
        """
        Returns validated params key/value pair dictionary using predefined schema that casts objects to python types.

        Arguments
        schema -- Schema object (see schema module).
        param_names -- (Optional) Parameters to filter out for validation.
        """
        try:
            params = schema.validate(params)
        except Exception as e:
            logger.error(e)
            error_message = str(e).replace('Missing keys:', 'Missing field(s):')
            raise admin.InternalException(error_message)
        return params
    def password_create(self, username, password):
        """
        Creates a password entry using the storage/passwords endpoint. This endpoint will validate successful creationof the password by comparing length and hashes of the provided password and the retrieved cleartext password. Password realm will include a unique GUID.

        Arguments
        username -- The username associated with the provided password
        password -- The actual password which will be encrypted and stored in passwords.conf
        """
        m = hashlib.md5()
        m.update(password)
        password_orig_hash = m.hexdigest()

        realm = str(uuid.uuid4().hex)

        passwords_conf_postargs = {
            'realm': realm,
            'name': username,
            'password': password
        }

        passwords_rest_path = '/servicesNS/%s/%s/storage/passwords/' % (
            'nobody', self.appName)

        # Create password
        passwords_conf_payload = self.simple_request_eai(
            passwords_rest_path, 'create', 'POST', passwords_conf_postargs)
        password_link_alternate = passwords_conf_payload['entry'][0]['links'][
            'alternate']

        # Load password to check hash and length
        passwords_conf_payload = self.simple_request_eai(
            password_link_alternate, 'list', 'GET')
        password_after = passwords_conf_payload['entry'][0]['content'][
            'clear_password']

        m = hashlib.md5()
        m.update(password_after)
        password_after_hash = m.hexdigest()

        try:
            self.hash_len_confirm(password, password_after, password_orig_hash,
                                  password_after_hash)
        except Exception, e:
            logger.error(e)
            raise admin.InternalException('Password stored incorrectly %s' % e)
Example #10
0
    def handleCreate(self, confInfo):
        args = self.callerArgs.data

        # Sanity checking for app ID: no special chars and shorter than 100 chars
        appName = self.callerArgs.id
        if not appName or len(appName) == 0:
            raise admin.ArgValidationException('App folder name is not set.')

        if re.search('[^A-Za-z0-9._-]', appName):
            raise admin.ArgValidationException(
                'App folder name cannot contain spaces or special characters.')

        if len(appName) > 100:
            raise admin.ArgValidationException(
                'App folder name cannot be longer than 100 characters.')

        kwargs = {
            'label': _getFieldValue(args, HTTP_POST_LABEL, appName,
                                    maxLen=100),
            'visible': _getFieldValue(args, HTTP_POST_VISIBLE, 'true'),
            'author': _getFieldValue(args, HTTP_POST_AUTHOR, '', maxLen=100),
            'description': _getFieldValue(args, HTTP_POST_DESC, '',
                                          maxLen=500),
            'configured': _getFieldValue(args, HTTP_POST_CONFIGURED, '0'),
            'version': _getFieldValue(args, HTTP_POST_VERSION, '1.0.0')
        }
        template = _getFieldValue(args, HTTP_POST_TEMPLATE, DEFAULT_TEMPLATE)

        if re.match("^\d{1,3}\.\d{1,3}\.\d{1,3}(\s?\w[\w\d]{,9})?$",
                    kwargs['version']) is None:
            raise admin.ArgValidationException(
                "Version '%s' is invalid. Use the version format 'major.minor.patch', for example '1.0.0'."
                % kwargs['version'])

        try:
            url = appbuilder.createApp(appName, template, **kwargs)
            appbuilder.addUploadAssets(appName)
        except splunk.RESTException as e:
            raise admin.InternalException(e.msg)

        confInfo[appName].append('name', appName)
        for field in kwargs:
            confInfo[appName].append(field, kwargs[field])
Example #11
0
 def __init__(self, app=None, owner=None, session_key=None):
     self._app = app
     self._owner = 'nobody'  # so that conf file will be saved in app
     self._sessionKey = session_key
     splunkd_host_port = self._get_entity(CONF_WEB, 'settings').get(
         'mgmtHostPort', '127.0.0.1:8089')
     host_and_port = splunkd_host_port.split(':')
     self.local_splunk_host = host_and_port[0]
     self.local_splunk_port = host_and_port[1]
     logger.info('app %s, owner %s, host %s, port %s' %
                 (self._app, self._owner, self.local_splunk_host,
                  self.local_splunk_port))
     self._service = client.Service(host=self.local_splunk_host,
                                    port=self.local_splunk_port,
                                    app=self._app,
                                    owner=self._owner,
                                    token=self._sessionKey)
     if "Splunk_TA_snow" not in self._service.apps:
         raise admin.InternalException(
             "Splunk ServiceNow Add-on is not found on server")
     if CONF_TARGET not in self._service.confs:
         self._service.confs.create(CONF_TARGET)
        default_get_args = dict(output_mode='json')
        if get_args:
            default_get_args.update(get_args)

        logger.info('%s request %s' % (method, url))
        try:
            response, content = rest.simpleRequest(
                url,
                getargs=default_get_args,
                postargs=params,
                method=method,
                sessionKey=self.getSessionKey())
        except Exception, e:
            logger.error(e)
            raise admin.ServiceUnavailableException('Unable to %s %s entry.' %
                                                    (action, url))
        try:
            payload = json.loads(content)
        except Exception, e:
            logger.error(e)
            raise admin.InternalException(
                'Unable to parse %s response payload.' % url)
        if response.status not in [200, 201]:
            message = self.simple_request_messages_to_str(response.messages)
            logger.error(
                'handler_message="request failed, status code not in successful range" status="%s" params="%s" splunkd_message="%s"'
                % (response.status, params, message))
            raise admin.AlreadyExistsException('Unable to %s %s entry. %s' %
                                               (action, url, message))
        return payload
            ensemble_aws_accounts_conf_postargs[
                'cloudformation_stack_id'] = response['StackId']

        if params['cloudformation_template_action'] and params[
                'cloudformation_template_action'] == 'remove':
            try:
                client = boto3.client(
                    'cloudformation',
                    aws_access_key_id=params['aws_access_key'],
                    aws_secret_access_key=SECRET_KEY)
                response = client.delete_stack(StackName=params['name'])
            except Exception, e:
                logger.error(e)
                raise admin.InternalException(
                    'Error connecting to AWS or deleting CloudFormation template %s'
                    % e)

        if params['template_link_alternate'] and params[
                'template_link_alternate'] != '' and params[
                    'cloudformation_template_action'] and params[
                        'cloudformation_template_action'] == 'update':
            # Get CloudFormation template string
            cloudformation_templates_conf_payload = self.simple_request_eai(
                params['template_link_alternate'], 'list', 'GET')
            template_filename = cloudformation_templates_conf_payload['entry'][
                0]['content']['filename']

            with open(
                    os.path.dirname(os.path.abspath(__file__)) +
                    '/cloudformation_templates/' +
    def handleEdit(self, confInfo):
        """
        Called when user invokes the 'edit' action. Index modification is not supported through this endpoint. Both the
        scripted input and the ensemble_aws_accounts.conf stanza will be overwritten on ANY call to this endpoint.

        Arguments
        confInfo -- The object containing the information about what is being requested.
        """
        logger.info('Ensemble AWS Account edit requested.')

        name = self.callerArgs.id
        conf_stanza = urllib.quote_plus(name)
        params = self.validate_server_schema_params()
        conf_handler_path = '%s/%s' % (self.get_conf_handler_path_name(
            'ensemble_aws_accounts', 'nobody'), conf_stanza)

        ensemble_aws_accounts_eai_response_payload = self.simple_request_eai(
            conf_handler_path, 'list', 'GET')
        old_aws_access_key = ensemble_aws_accounts_eai_response_payload[
            'entry'][0]['content']['aws_access_key']
        old_aws_secret_key_link_alternate = ensemble_aws_accounts_eai_response_payload[
            'entry'][0]['content']['aws_secret_key_link_alternate']

        # Create post args - remove name to ensure edit instead of create
        ensemble_aws_accounts_conf_postargs = {
            'aws_access_key': params['aws_access_key'],
            'tags': params['tags'],
        }

        # Change password if provided in params
        if old_aws_access_key != params['aws_access_key']:
            if self.get_param('aws_secret_key'):
                # New username and password provided
                auth_params = self.validate_auth_schema_params()
                params.update(auth_params)
                # Edit passwords.conf stanza
                ensemble_aws_accounts_conf_postargs[
                    'aws_secret_key_link_alternate'] = self.password_edit(
                        old_aws_secret_key_link_alternate,
                        params['aws_access_key'], params['aws_secret_key'])
            else:
                # Can't change username without providing password
                raise admin.InternalException(
                    'AWS Secret Key must be provided on AWS Access Key change.'
                )
        if (old_aws_access_key == params['aws_access_key']
                and self.get_param('aws_secret_key')):
            # Password update to existing username
            auth_params = self.validate_auth_schema_params()
            params.update(auth_params)
            # Edit passwords.conf stanza
            ensemble_aws_accounts_conf_postargs[
                'aws_secret_key_link_alternate'] = self.password_edit(
                    old_aws_secret_key_link_alternate,
                    params['aws_access_key'], params['aws_secret_key'])

        if self.get_param('aws_secret_key'):
            aws_secret_key_link_alternate = self.get_param('aws_secret_key')
        else:
            aws_secret_key_link_alternate = old_aws_secret_key_link_alternate

        # Get AWS Secret Key
        passwords_conf_payload = self.simple_request_eai(
            aws_secret_key_link_alternate, 'list', 'GET')
        SECRET_KEY = passwords_conf_payload['entry'][0]['content'][
            'clear_password']

        if params['template_link_alternate'] and params[
                'template_link_alternate'] != '' and params[
                    'cloudformation_template_action'] and params[
                        'cloudformation_template_action'] == 'apply':
            # Get CloudFormation template string
            cloudformation_templates_conf_payload = self.simple_request_eai(
                params['template_link_alternate'], 'list', 'GET')
            template_filename = cloudformation_templates_conf_payload['entry'][
                0]['content']['filename']

            with open(
                    os.path.dirname(os.path.abspath(__file__)) +
                    '/cloudformation_templates/' +
                    template_filename) as json_file:
                json_data = json.dumps(json.load(json_file))

            try:
                client = boto3.client(
                    'cloudformation',
                    aws_access_key_id=params['aws_access_key'],
                    aws_secret_access_key=SECRET_KEY)
                response = client.create_stack(StackName=params['name'],
                                               TemplateBody=json_data,
                                               Capabilities=['CAPABILITY_IAM'])
            except Exception, e:
                logger.error(e)
                raise admin.InternalException(
                    'Error connecting to AWS or deploying CloudFormation template %s'
                    % e)

            ensemble_aws_accounts_conf_postargs[
                'cloudformation_stack_id'] = response['StackId']
Example #15
0
def installApp(location, force=False):
    installer = bundle_paths.BundleInstaller()
    location = location.strip()

    try:
        if location.startswith('http'):
            req = urllib2.Request(url=location)
            return installer.install_from_url(req, force)
        else:
            return installer.install_from_tar(location, force)
    except splunk.ResourceNotFound, e:
        raise admin.ArgValidationException(e.msg)
    except splunk.RESTException, e:
        if e.statusCode == 409:
            raise admin.AlreadyExistsException(e.msg)
        raise admin.InternalException(e.msg)
    except Exception, e:
        raise admin.InternalException(e)


'''
Merges local and default parts of an app into default 
Returns the path to the merged app.
'''


def mergeApp(appName):
    appPath = _getAppPath(appName, True)
    if not appPath:
        return None
    tmpPath = os.path.join(PACKAGE_PATH, 'DELETEME_' + appName)
Example #16
0
    def handleCreate(self, confInfo):
        """
        Called when user invokes the "create" action.

        Arguments
        confInfo -- The object containing the information about what is being requested.
        """
        logger.info('Bulk credential upload requested.')

        # Validate and extract correct POST params
        params = self.validate_bulk_credential_upload_schema_params()

        grand_central_aws_accounts_rest_path = '/servicesNS/%s/%s/grand_central_aws_accounts' % (
            'nobody', self.appName)

        grand_central_aws_accounts_eai_response_payload = self.simple_request_eai(
            grand_central_aws_accounts_rest_path, 'list', 'GET')
        grand_central_aws_accounts = {}

        for grand_central_aws_account in grand_central_aws_accounts_eai_response_payload[
                'entry']:
            grand_central_aws_accounts[grand_central_aws_account['content'][
                'aws_account_id']] = grand_central_aws_account['content']

        # Get CloudFormation template from params or from template link alternate
        if 'credential_file' in params and params[
                'credential_file'] != '0' and params['credential_file'] != '':
            credentials = json.loads(
                params['credential_file'])[0]['credentials']
        else:
            credentials = []

        for credential in credentials:
            # Get the acct id of the credential
            aws_access_key = credential['key_id']
            SECRET_KEY = credential['secret_key']
            try:
                client = boto3.client('sts',
                                      aws_access_key_id=aws_access_key,
                                      aws_secret_access_key=SECRET_KEY)

                response = client.get_caller_identity()

            except Exception, e:
                logger.error(e)
                raise admin.InternalException('Error connecting to AWS %s' % e)

            if response['Account'] in grand_central_aws_accounts:
                # update the GC acct credential
                post_args = {
                    'display_name':
                    grand_central_aws_accounts[response['Account']]
                    ['display_name'],
                    'aws_account_id':
                    response['Account'],
                    'aws_access_key':
                    aws_access_key,
                    'aws_secret_key':
                    SECRET_KEY,
                }

                if 'aws_account_arn' not in grand_central_aws_accounts[
                        response['Account']] or grand_central_aws_accounts[
                            response['Account']]['aws_account_arn'] == '':
                    post_args['aws_account_arn'] = response['Arn']

                grand_central_aws_account_rest_path = '%s/%s' % (
                    grand_central_aws_accounts_rest_path, response['Account'])

                grand_central_aws_accounts_eai_response_payload = self.simple_request_eai(
                    grand_central_aws_account_rest_path, 'edit', 'POST',
                    post_args)

            else:
                # create a new GC acct
                post_args = {
                    'name': response['Account'],
                    'display_name': response['Account'],
                    'aws_account_id': response['Account'],
                    'aws_access_key': aws_access_key,
                    'aws_secret_key': SECRET_KEY,
                    'aws_account_arn': response['Arn']
                }

                grand_central_aws_accounts_eai_response_payload = self.simple_request_eai(
                    grand_central_aws_accounts_rest_path, 'create', 'POST',
                    post_args)
    def handleEdit(self, confInfo):
        """
        Called when user invokes the 'edit' action. Index modification is not supported through this endpoint. Both the
        scripted input and the grand_central_aws_accounts.conf stanza will be overwritten on ANY call to this endpoint.

        Arguments
        confInfo -- The object containing the information about what is being requested.
        """
        logger.info('Grand Central AWS Account edit requested.')

        name = self.callerArgs.id
        conf_stanza = urllib.quote_plus(name)
        params = self.validate_server_schema_params()
        conf_handler_path = '%s/%s' % (self.get_conf_handler_path_name(
            'grand_central_aws_accounts', 'nobody'), conf_stanza)

        grand_central_aws_accounts_eai_response_payload = self.simple_request_eai(
            conf_handler_path, 'list', 'GET')
        old_aws_access_key = grand_central_aws_accounts_eai_response_payload[
            'entry'][0]['content']['aws_access_key']
        old_aws_secret_key_link_alternate = grand_central_aws_accounts_eai_response_payload[
            'entry'][0]['content']['aws_secret_key_link_alternate']

        # Create post args - remove name to ensure edit instead of create

        grand_central_aws_accounts_conf_postargs = {
            'aws_access_key': params['aws_access_key'],
            'display_name': params['display_name'],
        }

        # Handle optional post args
        if 'tags' in params and params['tags'] != '':
            grand_central_aws_accounts_conf_postargs['tags'] = params['tags']
        if 'aws_account_status' in params and params[
                'aws_account_status'] != '':
            grand_central_aws_accounts_conf_postargs[
                'aws_account_status'] = params['aws_account_status']
        if 'aws_account_email' in params and params['aws_account_email'] != '':
            grand_central_aws_accounts_conf_postargs[
                'aws_account_email'] = params['aws_account_email']
        if 'aws_account_arn' in params and params['aws_account_arn'] != '':
            grand_central_aws_accounts_conf_postargs[
                'aws_account_arn'] = params['aws_account_arn']
        if 'aws_account_joined_method' in params and params[
                'aws_account_joined_method'] != '':
            grand_central_aws_accounts_conf_postargs[
                'aws_account_joined_method'] = params[
                    'aws_account_joined_method']
        if 'aws_account_joined_timestamp' in params and params[
                'aws_account_joined_timestamp'] != '':
            grand_central_aws_accounts_conf_postargs[
                'aws_account_joined_timestamp'] = params[
                    'aws_account_joined_timestamp']
        if 'splunk_account_link_alternate' in params and params[
                'splunk_account_link_alternate'] != '':
            grand_central_aws_accounts_conf_postargs[
                'splunk_account_link_alternate'] = params[
                    'splunk_account_link_alternate']
        if 'parent_aws_account_id' in params and params[
                'parent_aws_account_id'] != '':
            grand_central_aws_accounts_conf_postargs[
                'parent_aws_account_id'] = params['parent_aws_account_id']

        # Change password if provided in params
        if old_aws_access_key != params['aws_access_key']:
            if self.get_param('aws_secret_key'):
                # New username and password provided
                auth_params = self.validate_auth_schema_params()
                params.update(auth_params)
                # Edit passwords.conf stanza
                grand_central_aws_accounts_conf_postargs[
                    'aws_secret_key_link_alternate'] = self.password_edit(
                        old_aws_secret_key_link_alternate,
                        params['aws_access_key'], params['aws_secret_key'])
            else:
                # Can't change username without providing password
                raise admin.InternalException(
                    'AWS Secret Key must be provided on AWS Access Key change.'
                )
        if (old_aws_access_key == params['aws_access_key']
                and self.get_param('aws_secret_key')):
            # Password update to existing username
            auth_params = self.validate_auth_schema_params()
            params.update(auth_params)
            # Edit passwords.conf stanza
            grand_central_aws_accounts_conf_postargs[
                'aws_secret_key_link_alternate'] = self.password_edit(
                    old_aws_secret_key_link_alternate,
                    params['aws_access_key'], params['aws_secret_key'])

        # Edit grand_central_aws_accounts.conf
        grand_central_aws_accounts_eai_response_payload = self.simple_request_eai(
            conf_handler_path, 'edit', 'POST',
            grand_central_aws_accounts_conf_postargs)

        # Always populate entry content from request to list handler.
        grand_central_aws_accounts_rest_path = '/servicesNS/%s/%s/grand_central_aws_accounts/%s' % (
            'nobody', self.appName, conf_stanza)
        grand_central_aws_accounts_eai_response_payload = self.simple_request_eai(
            grand_central_aws_accounts_rest_path, 'read', 'GET')
        self.set_conf_info_from_eai_payload(
            confInfo, grand_central_aws_accounts_eai_response_payload)