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))
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)
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())
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
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)
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)
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))
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)
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])
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']
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)
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)