Exemple #1
0
    def parse_vals(self, item):
        '''
        Recursive function to attempt to pull out the various settings from
        the scan editor.
        '''
        resp = dict()
        if 'id' in item and (
                'default' in item or
            ('type' in item and item['type']
             in ['file', 'checkbox', 'entry', 'medium-fixed-entry'])):
            # if we find both an 'id' and a 'default' attribute, or if we find
            # a 'type' attribute matching one of the known attribute types, then
            # we will parse out the data and append it to the response dictionary
            if not 'default' in item:
                item['default'] = ""
            resp[item['id']] = item['default']

        for key in item.keys():
            # here we will attempt to recurse down both a list of sub-
            # documents and an explicitly defined sub-document within the
            # editor data-structure.
            if key == 'modes':
                continue
            if (isinstance(item[key], list) and len(item[key]) > 0
                    and isinstance(item[key][0], dict)):
                for i in item[key]:
                    resp = dict_merge(resp, self.parse_vals(i))
            if isinstance(item[key], dict):
                resp = dict_merge(resp, self.parse_vals(item[key]))

        # Return the key-value pair.
        return resp
Exemple #2
0
    def _constructor(self, *filters, **kw):
        '''
        Handles parsing the keywords and returns a query definition document
        '''
        query = self._query_constructor(*filters, **kw)
        kw = dict_merge(kw, query.get('query', dict()))

        if 'name' in kw:
            self._check('name', kw['name'], str)

        if 'description' in kw:
            self._check('description', kw['description'], str)

        if 'tags' in kw:
            self._check('tags', kw['tags'], str)

        if 'sort_field' in kw:
            kw['sortField'] = self._check(
                'sort_field', kw['sort_field'], str)
            del(kw['sort_field'])

        if 'sort_direction' in kw:
            kw['sortDir'] = self._check(
                'sort_direction', kw['sort_direction'], str,
                choices=['ASC', 'DESC'], case='upper')
            del(kw['sort_direction'])

        if 'offset' in kw:
            kw['startOffset'] = self._check('offset', kw['offset'], int)
            del(kw['offset'])

        if 'limit' in kw:
            kw['endOffset'] = self._check('limit', kw['limit'], int)
            del(kw['limit'])

        if 'owner_id' in kw:
            kw['ownerID'] = str(self._check(
                'owner_id', kw['owner_id'], int))
            del(kw['owner_id'])

        if 'context' in kw:
            self._check('context', kw['context'], str)

        if 'browse_cols' in kw:
            kw['browseColumns'] = ','.join(self._check(
                'browse_cols', kw['browse_cols'], list))
            del(kw['browse_cols'])

        if 'browse_sort_col' in kw:
            kw['browseSortColumn'] = self._check(
                'browse_sort_col', kw['browse_sort_col'], str)
            del(kw['browse_sort_col'])

        if 'browse_sort_direction' in kw:
            kw['browseSortDirection'] = self._check(
                'browse_sort_direction', kw['browse_sort_direction'],
                str, case='upper', choices=['ASC', 'DESC'])
            del(kw['browse_sort_direction'])

        return kw
    def edit(self, id, **kw):
        '''
        Edit an existing target group.

        `target-groups: edit <https://cloud.tenable.com/api#/resources/target-groups/edit>`_

        Args:
            id (int): The unique identifier for the target group.
            name (str, optional): The name of the target group.
            members (list, optional):
                The members of the target group.  FQDNs, CIDRs, IPRanges, and
                individual IPs are supported.  NOTE: modifying the member list
                is atomic and not additive.  All previous members that are
                desired to be kept within the member list much also be included.
            acls (list, optional):
                A list of ACLs defining how the asset list can be used.  For
                further information on how the ACL dictionaries should be
                written, please refer to the API documentation.  NOTE: modifying
                ACLs is atomic and not additive.  Please provide the complete
                list of ACLs that this asset group will need.
            type (str, optional):
                The type of target group to create.  Valid types are `user` and
                `system`.

        Returns:
            dict: The modified target group resource record.

        Examples:
            >>> tio.target_groups.edit(1, name='Updated TG Name')
        '''
        payload = dict()

        if 'name' in kw:
            payload['name'] = self._check('name', kw['name'], str)
        if 'acls' in kw:
            payload['acls'] = self._check('acls', kw['acls'], list)
        if 'type' in kw:
            payload['type'] = self._check('type',
                                          kw['type'],
                                          str,
                                          choices=['system', 'user'])
        if 'members' in kw and len(kw['members']) > 0:
            payload['members'] = ','.join(
                self._check('members', kw['members'], list))

        # We need to get the current asset group and then merge in the modified
        # data.  We will store the information in the same variable as the
        # modified information was built into.
        for agroup in self.list():
            if agroup['id'] == self._check('id', id, int):
                craw = agroup
        current = {
            'name': craw['name'],
            'acls': craw['acls'],
            'type': craw['type'],
            'members': craw['members'],
        }
        payload = dict_merge(current, payload)
        return self._api.put('target-groups/{}'.format(id),
                             json=payload).json()
Exemple #4
0
    def template_details(self, id, fields=None, remove_editor=True):
        '''
        Retrieves the details for a specified policy template.

        :sc-api:`scan-policy: template-details <Scan-Policy-Templates.html#policyTemplate_id_GET>`

        Args:
            id (int): The unique identifier for the policy template
            fields (list, optional):
                The list of fields that are desired to be returned.  For details
                on what fields are available, please refer to the details on the
                request within the policy template details API doc.
            remove_editor (bol, optional):
                Should the response have the raw editor string removed?  The
                default is yes.

        Returns:
            :obj:`dict`:
                Details about the scan policy template

        Examples:
            >>> template = sc.policies.template_details(2)
            >>> pprint(template)
        '''
        params = dict()
        if fields:
            params['fields'] = ','.join([
                self._check('field', f, str)
                for f in self._check('fields', fields, list)
            ])

        resp = self._api.get('policyTemplate/{}'.format(
            self._check('id', id, int)),
                             params=params).json()['response']

        if 'editor' in resp:
            # Everything is packed JSON, so lets decode the JSON documents into
            editor = json.loads(resp['editor'])

            # Now to decompose the embeddable credentials settings.  What we
            # intend to do here is return the default settings for every
            # credential set that can be returned.
            resp['credentials'] = dict()
            if 'credentials' in editor:
                emcreds = json.loads(editor['credentials'])
                for group in emcreds['groups']:
                    for item in group['credentials']:
                        resp['credentials'][item['id']] = policy_settings(item)

            # Now to perform the same action as we did for the credentials with
            # the policy preferences as well.
            resp['preferences'] = dict()
            for section in editor['sections']:
                if section['id'] != 'setup':
                    resp['preferences'] = dict_merge(resp['preferences'],
                                                     policy_settings(section))
            if remove_editor:
                del (resp['editor'])
        return resp
Exemple #5
0
    def create(self, **kw):
        '''
        Creates a new scan policy

        :sc-api:`scan-policy: create <Scan-Policy.html#policy_POST>`

        Args:
            name (str): The Name of the new scan policy
            audit_files (list, optional):
                A list of audit files (by integer id) to be used for the
                scan policy.
            description (str, optional):
                An optional description for the policy
            preferences (dict, optional):
                A dictionary of settings that override the defaults within a
                policy template.
            profile_name (str, optional):
                The profile of the scan.  Default is an empty string.
            owner_id (int, optional):
                Define who shall own the policy by that user's integer identifier
            tags (str, optional):
                An optional tag identifier for the policy
            template_id (int, optional):
                The identifier of the policy template to use.  If none is
                specified, the default id for the "Advanced Policy" will be
                used.
            xccdf (bool, optional):
                Should XCCDF results be generated?  The default is False.

        Returns:
            :obj:`dict`:
                The created scan policy resource.

        Examples:
            An example advanced policy with all of the default preferences.

            >>> sc.policies.create(
            ...     name='Example Advanced Policy')

            An example policy where we want to modify
        '''
        # Firstly we need to check that some specific values are set
        if 'name' not in kw:
            raise UnexpectedValueError('name is a required parameter')
        kw['template_id'] = self._check(
            'template_id', kw.get('template_id', 1), int)

        # Next we will pull the template details and then pull out the default
        # settings for the template.
        template = self.template_details(kw['template_id'])

        # Next, if there are any preferences that the user provided, we will
        # overlay those on top of the now constructed defaults.
        kw['preferences'] = dict_merge(template['preferences'],
            kw.get('preferences', dict()))

        policy = self._constructor(**kw)
        return self._api.post('policy', json=policy).json()['response']
Exemple #6
0
    def create(self, name, address, **kw):
        '''
        Creates a scanner.

        :sc-api:`scanner: create <Scanner.html#scanner_POST>`

        Args:
            address (str): The address of the scanner
            name (str): The name of the scanner
            agent_capable (bool, optional):
                Is this scanner an agent capable scanner?  If left unspecified
                the default is ``False``.
            description (str, optional):
                The description of the scanner.
            enabled (bool, optional):
                Is this scanner enabled?  If left unspecified, the default is
                ``True``.
            managed (bool, optional):
                Is the plugin set for this scanner managed?  If left unspecified
                then the default is ``False``.
            orgs (list, optional):
                If the scanner is an agent capable scanner, then a list of
                organization ids is to be specified to attach the scanner for
                the purposes of agent scanning.
            port (int, optional):
                What is the port that the Nessus service is running on.  If left
                unspecified, then the default is ``8834``.
            proxy (bool, optional):
                Is this scanner behind a proxy?  If left unspecified then the
                default is ``False``.
            zone_ids (list, optional):
                List of scan zones that this scanner is to be a member of.

        Returns:
            :obj:`dict`:
                The newly created scanner.

        Examples:
            >>> scanner = sc.scanners.create('Example Scanner', '192.168.0.1')
        '''
        payload = {
            'port': 8834,
            'proxy': False,
            'verify': False,
            'enabled': True,
            'managed': False,
            'agent_capable': False,
            'name': name,
            'address': address,
        }
        payload = self._constructor(**dict_merge(payload, kw))
        return self._api.post('scanner', json=payload).json()['response']
Exemple #7
0
    def edit(self,
             user_id,
             permissions=None,
             name=None,
             email=None,
             enabled=None):
        '''
        Modify an existing user.

        :devportal:`users: edit <users-edit>`

        Args:
            user_id (int): The unique identifier for the user.
            permissions (int, optional):
                The permissions role for the user.  The permissions integer
                is derived based on the desired role of the user.  For details
                describing what permissions values mean what roles, please refer
                to the `User Roles <https://cloud.tenable.com/api#/authorization>`_
                table to see what permissions are accepted.
            name (str, optional): The human-readable name of the user.
            email (str, optional): The email address of the user.
            enabled (bool, optional): Is the user account enabled?

        Returns:
            :obj:`dict`:
                The modified user resource record.

        Examples:
            >>> user = tio.users.edit(1, name='New Full Name')
        '''
        payload = dict()

        if permissions:
            payload['permissions'] = self._check('permissions', permissions,
                                                 int)
        if enabled is not None:
            payload['enabled'] = self._check('enabled', enabled, bool)
        if email:
            payload['email'] = self._check('email', email, str)
        if name:
            payload['name'] = self._check('name', name, str)

        # Merge the data that we build with the payload with the user details.
        user = self.details(self._check('user_id', user_id, int))
        payload = dict_merge(
            {
                'permissions': user['permissions'],
                'enabled': user['enabled'],
                'email': user['email'],
                'name': user.get('name', None),
            }, payload)
        return self._api.put('users/{}'.format(user_id), json=payload).json()
Exemple #8
0
    def configure(self, id, **kw):
        '''
        Overwrite the parameters specified on top of the existing scan record.

        :devportal:`scans: configure <scans-configure>`

        Args:
            id (int): The unique identifier for the scan.
            template (str, optional):
                The scan policy template to use.  If no template is specified
                then the default of `basic` will be used.
            policy (int, optional):
                The id or title of the scan policy to use (if not using one of
                the pre-defined templates).  Specifying a a policy id will
                override the the template parameter.
            targets (list, optional):
                If defined, then a list of targets can be specified and will
                be formatted to an appropriate text_target attribute.
            credentials (dict, optional):
                A list of credentials to use.
            compliance (dict, optional):
                A list of compliance audiots to use.
            scanner (str, optional):
                Define the scanner or scanner group uuid or name.
            **kw (dict, optional):
                The various parameters that can be passed to the scan creation
                API.  Examples would be `name`, `email`, `scanner_id`, etc.  For
                more detailed information, please refer to the API documentation
                linked above.  Further, any keyword arguments passed that are
                not explicitly documented will be automatically appended to the
                settings document.  There is no need to pass settings directly.

        Returns:
            :obj:`dict`:
                The scan resource record.

        Examples:
            >>> tio.scans.configure(1, name='New Scan Name')
        '''

        # We will get the current scan record, generate the new parameters in
        # the correct format, and then merge them together to create the new
        # :func:`~.tenable.tenable_io.ScansAPI.details` method, however is not
        # scan record that we will be pushing to the API.
        current = self.details(id)
        updated = self._create_scan_document(kw)
        scan = dict_merge(current, updated)

        # Performing the actual call to the API with the updated scan record.
        return self._api.put('scans/{}'.format(self._check('id', id, int)),
                             json=scan).json()
Exemple #9
0
    def edit(self, id, **kw):
        '''
        Edit an existing target group.

        :devportal:`target-groups: edit <target-groups-edit>`

        Args:
            id (int): The unique identifier for the target group.
            name (str, optional): The name of the target group.
            members (list, optional):
                The members of the target group.  FQDNs, CIDRs, IPRanges, and
                individual IPs are supported.  NOTE: modifying the member list
                is atomic and not additive.  All previous members that are
                desired to be kept within the member list much also be included.
            acls (list, optional):
                A list of ACLs defining how the asset list can be used.  For
                further information on how the ACL dictionaries should be
                written, please refer to the API documentation.  NOTE: modifying
                ACLs is atomic and not additive.  Please provide the complete
                list of ACLs that this asset group will need.

        Returns:
            :obj:`dict`:
                The modified target group resource record.

        Examples:
            >>> tio.target_groups.edit(1, name='Updated TG Name')
        '''
        payload = dict()

        if 'name' in kw:
            payload['name'] = self._check('name', kw['name'], str)
        if 'acls' in kw:
            payload['acls'] = self._check('acls', kw['acls'], list)
        if 'members' in kw and len(kw['members']) > 0:
            payload['members'] = ','.join(
                self._check('members', kw['members'], list))

        # We need to get the current asset group and then merge in the modified
        # data.  We will store the information in the same variable as the
        # modified information was built into.
        craw = self.details(self._check('id', id, int))
        current = {
            'name': craw.get('name'),
            'acls': craw.get('acls'),
            'members': craw.get('members'),
        }
        payload = dict_merge(current, payload)
        return self._api.put('target-groups/{}'.format(id),
                             json=payload).json()
Exemple #10
0
    def edit(self, id, **kw):
        '''
        Edits a scanner.

        + `scanner: edit <https://docs.tenable.com/sccv/api/Scanner.html#scanner_id_PATCH>`_

        Args:
            id (int): The numeric identifier for the scanner.
            address (str, optional): The address of the scanner
            agent_capable (bool, optional):
                Is this scanner an agent capable scanner?  If left unspecified
                the default is ``False``.
            description (str, optional):
                The description of the scanner.
            enabled (bool, optional):
                Is this scanner enabled?  If left unspecified, the default is
                ``True``.
            managed (bool, optional):
                Is the plugin set for this scanner managed?  If left unspecified
                then the default is ``False``.
            name (str, optional): The name of the scanner
            orgs (list, optional):
                If the scanner is an agent capable scanner, then a list of
                organization ids is to be specified to attach the scanner for
                the purposes of agent scanning.
            port (int, optional):
                What is the port that the Nessus service is running on.  If left
                unspecified, then the default is ``8834``.
            proxy (bool, optional):
                Is this scanner behind a proxy?  If left unspecified then the
                default is ``False``.
            zone_ids (list, optional):
                List of scan zones that this scanner is to be a member of.  
        
        Returns:
            dict: The newly updated scan zone. 
        
        Examples:
            >>> scanner = sc.scanners.create(1, enabled=True)
        '''
        base = self.details(self._check('id', id, int))
        payload = self._constructor(**kw)
        return self._api.patch('scanner/{}'.format(id),
                               json=dict_merge(base,
                                               payload)).json()['response']
Exemple #11
0
    def edit(self,
             cred_uuid,
             cred_name=None,
             description=None,
             permissions=None,
             ad_hoc=None,
             **settings):
        '''
        Creates a new managed credential.

        :devportal:`credentials: create <credentials-create>`

        Args:
            ad_hoc (bool, optional):
                Determins whether the credential is managed (``False``) or an
                embedded credential in a scan or policy (``True``).
            cred_name (str, optional):
                The name of the credential.
            description (str, optional):
                A description for the credential.
            permissions (list, optional):
                A list of permissions (in either tuple or native dict format)
                detailing whom is allowed to use or edit this credential set.
                For the dictionary format, refer to the API docs.  The tuple
                format uses the customary ``(type, perm, uuid)`` format.

                Examples:
                    - ``('user', 32, user_uuid)``
                    - ``('group', 32, group_uuid)``
                    - ``('user', 'use', user_uuid)``

            **settings (dict, optional):
                Additional keywords passed will be added to the settings dict
                within the API call.  As this dataset can be highly variable,
                it will not be validated and simply passed as-is.

        Returns:
            :obj:`bool`:
                The status of the update process.

        Examples:
            >>> cred_uuid = '00000000-0000-0000-0000-000000000000'
            >>> tio.credentials.edit(cred_uuid,
            ...     password='******',
            ...     escalation_password='******')
        '''
        current = self.details(cred_uuid)

        payload = {
            'name':
            self._check('cred_name', cred_name, str, default=current['name']),
            'description':
            self._check('description',
                        description,
                        str,
                        default=current['description']),
            'ad_hoc':
            self._check('ad_hoc', ad_hoc, bool, default=current['ad_hoc']),
        }
        if permissions:
            payload['permissions'] = self._permissions_constructor(permissions)
        payload['settings'] = dict_merge(current['settings'], settings)

        return self._api.put('credentials/{}'.format(cred_uuid),
                             json=payload).json()['updated']
Exemple #12
0
    def create(self, **kw):
        '''
        `scans: create <https://cloud.tenable.com/api#/resources/scans/create>`_

        Args:
            template (str, optional): 
                The scan policy template to use.  If no template is specified
                then the default of `basic` will be used.
            policy (str, optional):
                The UUID of the scan policy to use (if not using one of the
                pre-defined templates).  Specifying a a policy UUID will
                override the the template parameter.
            targets (list, optional):
                If defined, then a list of targets can be specified and will
                be formatted to an appropriate text_target attribute.
            credentials (dict, optional):
                A list of credentials to use.
            compliance (dict, optional):
                A list of compliance audiots to use.
            **kw (dict, optional):
                The various parameters that can be passed to the scan creation
                API.  Examples would be `name`, `email`, `scanner_id`, etc.  For
                more detailed informatiom, please refer to the API documentation
                linked above.

        Returns:
            dict: The scan resource record of the newly created scan.
        '''
        scan = {
            'settings': dict(),
        }

        # If a template is specified, then we will pull the listing of available
        # templates and set the policy UUID to match the template name given.
        if 'template' in kw:
            templates = self._api.policies.templates()
            scan['uuid'] = templates[self._check(
                'template', kw['template'], str, choices=templates.keys())]
            del(kw['template'])

        # If a policy UUID is sent, then we will set the scan template UUID to
        # be the UUID that was specified.
        if 'policy' in kw:
            scan['uuid'] = self._check('policy', kw['policy'], 'uuid')
            del(kw['policy'])

        # If the targets parameter is specified, then we will need to convert
        # the list of targets to a comma-delimited string and then set the
        # text_targets paramater with the result.
        if 'targets' in kw:
            scan['settings']['text_targets'] = ','.join(self._check(
                'targets', targets, list))
            del(kw['targets'])

        # For credentials, we will simply push the dictionary as-is into the
        # the credentials.add subdocument.
        if 'credentials' in kw:
            scan['credentials'] = {'add': dict()}
            scan['credentials']['add'] = self._check(
                'credentials', kw['credentials'], dict)
            del(kw['credentials'])

        # Just like with credentials, we will push the dictionary as-is into the
        # correct subdocument of the scan definition.
        if 'compliance' in kw:
            scan['audits'] = self._check('compliance', compliance, dict)
            del(kw['compliance'])

        # any other remaining keyword arguments will be passed into the settings
        # subdocument.  The bulk of the data should go here...
        scan['settings'] = dict_merge(scan['settings'], kw)

        # Run the API call and return the result to the caller.
        return self._api.post('scans', json=scan).json()['scan']
Exemple #13
0
    def details(self, scan_id):
        '''
        Calls the editor API and parses the scan config details to return a
        document that closely matches what the API expects to be POSTed or PUTed
        via the create and configure methods.  The compliance audits and
        credentials are populated into the 'current' sub-document for the
        relevent resources.

        Args:
            scan_id (int): The unique identifier for the scan.

        Returns:
            dict: The scan configuration resource.

        Please note that flatten_scan is reverse-engineered from the responses
        from the editor API and isn't guaranteed to work. 
        '''

        # Get the editor object
        editor = self._api.get('editor/scan/{}'.format(
            self._check('scan_id', scan_id, int))).json()

        # define the initial skeleton of the scan object
        scan = {
            'settings': self._api.editor.parse_vals(editor['settings']),
            'uuid': editor['uuid']
        }

        # graft on the basic settings that aren't stored in any input sections.
        for item in editor['settings']['basic']['groups']:
            for setting in item.keys():
                if setting not in ['name', 'title', 'inputs']:
                    scan['settings'][setting] = item[setting]

        if 'credentials' in editor:
            # if the credentials sub-document exists, then lets walk down the
            # credentials dataset
            scan['credentials'] = {
                'current': self._api.editor.parse_creds(
                    editor['credentials']['data'])
            }

            # We also need to gather the settings from the various credential
            # settings that are unique to the scan.
            for ctype in editor['credentials']['data']:
                for citem in ctype['types']:
                    if 'settings' in citem and citem['settings']:
                        scan['settings'] = dict_merge(
                            scan['settings'], self._api.editor.parse_vals(
                                citem['settings']))

        if 'compliance' in editor:
            # if the audits sub-document exists, then lets walk down the
            # audits dataset.
            scan['compliance'] = {
                'current': self._api.editor.parse_audits(
                    editor['compliance']['data'])
            }

            # We also need to add in the "compliance" settings into the scan
            # settings.
            for item in editor['compliance']['data']:
                if 'settings' in item:
                    scan['settings'] = dict_merge(
                        scan['settings'], self._api.editor.parse_vals(
                            item['settings']))

        if 'plugins' in editor:
            # if the plugins sub-document exists, then lets walk down the
            # plugins dataset.
            scan['plugins'] = self._api.editor.parse_plugins(
                editor['plugins']['families'], scan_id)

        # We next need to do a little post-parsing of the ACLs to find the
        # owner and put ownder_id attribute into the appropriate location.
        for acl in scan['settings']['acls']:
            if acl['owner'] == 1:
                scan['settings']['owner_id'] = acl['id']

        # return the scan document to the caller.
        return scan
Exemple #14
0
    def edit(self, id, **kw):
        '''
        Edits an access group

        :devportal:`access-groups: update <access-groups-edit>`

        Args:
            id (str):
                The UUID of the access group to edit.
            name (str, optional):
                The name of the access group to create.
            rules (list, optional):
                a list of rule tuples.  Tuples are defined in the standardized
                method of name, operator, value.  For example:

                .. code-block:: python

                    ('operating_system', 'eq', ['Windows NT'])

                Rules will be validate against by the filters before being sent
                to the API.  Note that the value field in this context is a list
                of string values.
            principals (list, optional):
                A list of principal tuples.  Each tuple must contain both the
                type and the identifier for the principal.  The identifier can
                be either a UUID associated to a user/group, or the name of the
                user/group.  For example:

                .. code-block:: python

                    ('user', '32a0c314-442b-4aed-bbf5-ba9cf5cafbf4')
                    ('user', '*****@*****.**')
                    ('group', '32a0c314-442b-4aed-bbf5-ba9cf5cafbf4')

            all_users (bool, optional):
                If enabled, the access group will apply to all users and any
                principals defined will be ignored.
            all_assets (bool, optional):
                Specifies if the access group to modify is the default
                "all assets" group or a user-defined one.
        '''

        # If any rules are specified, then run them through the filter parser.
        if 'rules' in kw:
            kw['rules'] = self._parse_filters(
                kw['rules'],
                self._api.filters.access_group_asset_rules_filters(),
                rtype='accessgroup')['rules']

        # if any principals are specified, then run them through the principal
        # parser.
        if 'principals' in kw:
            kw['principals'] = self._principal_constructor(kw['principals'])

        # get the details of the access group that we are supposed to be editing
        # and then merge in the keywords specified.
        g = dict_merge(self.details(self._check('id', id, 'uuid')), kw)

        # construct the payload from the merged details.
        payload = {
            'name': self._check('name', g['name'], str),
            'all_users': self._check('all_users', g['all_users'], bool),
            'all_assets': self._check('all_assets', g['all_assets'], bool),
            'rules': g['rules'],
            'principals': g['principals']
        }

        # call the API endpoint and return the response to the caller.
        return self._api.put('access-groups/{}'.format(id),
                             json=payload).json()
Exemple #15
0
    def template_details(self, name):
        '''
        Calls the editor API and parses the policy template config to return a
        document that closely matches what the API expects to be POSTed or PUTed
        via the policy create and configure methods.  The compliance audits and
        credentials are populated into the 'current' sub-document for the
        relevant resources.

        Args:
            name (str): The name of the scan .

        Returns:
            dict: The policy configuration resource.

        Examples:
            >>> template = tio.policies.template('basic')
            >>> pprint(template)

        Please note that template_details is reverse-engineered from the
        responses from the editor API and isn't guaranteed to work.
        '''

        # Get the policy template UUID
        tmpl = self.templates()
        tmpl_uuid = tmpl[self._check('name', name, str, choices=tmpl.keys())]

        # Get the editor object
        editor = self._api.get('editor/policy/templates/{}'.format(
            self._check('tmpl_uuid', tmpl_uuid, str))).json()

        # define the initial skeleton of the scan object
        scan = {
            'settings': policy_settings(editor['settings']),
            'uuid': editor['uuid']
        }

        # graft on the basic settings that aren't stored in any input sections.
        for item in editor['settings']['basic']['groups']:
            for setting in item.keys():
                if setting not in ['name', 'title', 'inputs']:
                    scan['settings'][setting] = item[setting]

        if 'credentials' in editor:
            # if the credentials sub-document exists, then lets walk down the
            # credentials dataset
            scan['credentials'] = {
                'current':
                self._api.editor.parse_creds(editor['credentials']['data'])
            }

            # We also need to gather the settings from the various credential
            # settings that are unique to the scan.
            for ctype in editor['credentials']['data']:
                for citem in ctype['types']:
                    if 'settings' in citem and citem['settings']:
                        scan['settings'] = dict_merge(
                            scan['settings'],
                            policy_settings(citem['settings']))

        if 'compliance' in editor:
            # if the audits sub-document exists, then lets walk down the
            # audits dataset.
            scan['compliance'] = {
                'current':
                self._api.editor.parse_audits(editor['compliance']['data'])
            }

            # We also need to add in the "compliance" settings into the scan
            # settings.
            for item in editor['compliance']['data']:
                if 'settings' in item:
                    scan['settings'] = dict_merge(
                        scan['settings'], policy_settings(item['settings']))
        print(editor['plugins']['families'])
        if 'plugins' in editor:
            # if the plugins sub-document exists, then lets walk down the
            # plugins dataset.
            scan['plugins'] = self._api.editor.parse_plugins(
                editor['plugins']['families'], tmpl_uuid)

        return scan
Exemple #16
0
    def _create_scan_document(self, kw):
        '''
        Takes the keyworded arguments and will provide a scan settings document
        based on the values inputted.

        Args:
            kw (dict): The keyword dict passed from the user

        Returns:
            scan (dict): The resulting scan document based on the kw provided.
        '''
        scan = {
            'settings': dict(),
        }

        # If a template is specified, then we will pull the listing of available
        # templates and set the policy UUID to match the template name given.
        if 'template' in kw:
            templates = self._api.policies.templates()
            scan['uuid'] = templates[self._check('template',
                                                 kw['template'],
                                                 str,
                                                 default='basic',
                                                 choices=list(
                                                     templates.keys()))]
            del (kw['template'])

        # If a policy UUID is sent, then we will set the scan template UUID to
        # be the UUID that was specified.
        if 'policy' in kw:
            try:
                # at first we are going to assume that the information that was
                # relayed to use for the scan policy was the policy ID.  As
                # this is the least expensive thing to check for, it's a logical
                # starting point.
                scan['settings']['policy_id'] = self._check(
                    'policy', kw['policy'], int)

            except (UnexpectedValueError, TypeError):
                # Now we are going to attempt to find the scan policy based on
                # the title of the policy before giving up and throwing. an
                # UnexpectedValueError
                policies = self._api.policies.list()
                match = False
                for item in policies:
                    if kw['policy'] == item['name']:
                        scan['uuid'] = item['template_uuid']
                        scan['settings']['policy_id'] = item['id']
                        match = True
                if not match:
                    raise UnexpectedValueError('policy setting is invalid.')
            del (kw['policy'])

        # if the scanner attribute was set, then we will attempt to figure out
        # what scanner to use.
        if 'scanner' in kw:
            scanners = self._api.scanners.allowed_scanners()
            try:
                # we will always want to attempt to use the UUID first as it's
                # the cheapest check that we can run.
                scan['settings']['scanner_id'] = self._check(
                    'scanner',
                    kw['scanner'],
                    'scanner-uuid',
                    choices=[s['id'] for s in scanners])

            except (UnexpectedValueError, TypeError):
                # as an UnexpectedValueError was raised, the data may just be
                # the name of a scanner.  If this is the case, then we will want
                # to attempt to enumerate the scanner list and if we see a match,
                # use that scanner's UUID instead.
                for item in scanners:
                    if item['name'] == kw['scanner']:
                        scan['settings']['scanner_id'] = item['id']

                if 'scanner_id' not in scan['settings']:
                    raise UnexpectedValueError('scanner setting is invalid.')
            del (kw['scanner'])

        # If the targets parameter is specified, then we will need to convert
        # the list of targets to a comma-delimited string and then set the
        # text_targets paramater with the result.
        if 'targets' in kw:
            scan['settings']['text_targets'] = ','.join(
                self._check('targets', kw['targets'], list))
            del (kw['targets'])

        # For credentials, we will simply push the dictionary as-is into the
        # the credentials.add subdocument.
        if 'credentials' in kw:
            scan['credentials'] = {'add': dict()}
            scan['credentials']['add'] = self._check('credentials',
                                                     kw['credentials'], dict)
            del (kw['credentials'])

        # Just like with credentials, we will push the dictionary as-is into the
        # correct subdocument of the scan definition.
        if 'compliance' in kw:
            scan['audits'] = self._check('compliance', kw['compliance'], dict)
            del (kw['compliance'])

        if 'plugins' in kw:
            scan['plugins'] = self._check('plugins', kw['plugins'], dict)
            del (kw['plugins'])

        # any other remaining keyword arguments will be passed into the settings
        # subdocument.  The bulk of the data should go here...
        scan['settings'] = dict_merge(scan['settings'], kw)
        return scan
Exemple #17
0
    def _create_scan_document(self, kw):
        '''
        Takes the key-worded arguments and will provide a scan settings document
        based on the values inputted.

        Args:
            kw (dict): The keyword dict passed from the user

        Returns:
            :obj:`dict`:
                The resulting scan document based on the kw provided.
        '''
        scan = {
            'settings': dict(),
        }

        # If a template is specified, then we will pull the listing of available
        # templates and set the policy UUID to match the template name given.
        if 'template' in kw:
            templates = self._api.policies.templates()
            scan['uuid'] = templates[self._check('template',
                                                 kw['template'],
                                                 str,
                                                 default='basic',
                                                 choices=list(
                                                     templates.keys()))]
            del (kw['template'])

        # If a policy UUID is sent, then we will set the scan template UUID to
        # be the UUID that was specified.
        if 'policy' in kw:
            policies = self._api.policies.list()
            match = False

            # Here we are going to iterate over each policy in the list, looking
            # to see if we see a match in either the name or the id.  If we do
            # find a match, then we will use the first one that matches, pull
            # the editor config, and then use the policy id and scan policy
            # template uuid.
            for item in policies:
                if kw['policy'] in [item['name'], item['id']] and not match:
                    policy_tmpl = self._api.editor.details(
                        'scan/policy', item['id'])
                    scan['uuid'] = policy_tmpl['uuid']
                    scan['settings']['policy_id'] = item['id']
                    match = True

            # if no match was discovered, then raise an invalid warning.
            if not match:
                raise UnexpectedValueError('policy setting is invalid.')
            del (kw['policy'])

        # if the scanner attribute was set, then we will attempt to figure out
        # what scanner to use.
        if 'scanner' in kw:
            scanners = self._api.scanners.allowed_scanners()

            # We will want to attempt to enumerate the scanner list and if
            # we see a name match, replace the scanner name with the UUID
            # of the scanner instead.
            for item in scanners:
                if item['name'] == kw['scanner']:
                    kw['scanner'] = item['id']

            # we will always want to attempt to use the UUID first as it's
            # the cheapest check that we can run.
            scan['settings']['scanner_id'] = self._check(
                'scanner',
                kw['scanner'],
                'scanner-uuid',
                choices=[s['id'] for s in scanners])
            del (kw['scanner'])

        # If the targets parameter is specified, then we will need to convert
        # the list of targets to a comma-delimited string and then set the
        # text_targets parameter with the result.
        if 'targets' in kw:
            scan['settings']['text_targets'] = ','.join(
                self._check('targets', kw['targets'], list))
            del (kw['targets'])

        # For credentials, we will simply push the dictionary as-is into the
        # the credentials.add sub-document.
        if 'credentials' in kw:
            scan['credentials'] = {'add': dict()}
            scan['credentials']['add'] = self._check('credentials',
                                                     kw['credentials'], dict)
            del (kw['credentials'])

        # Just like with credentials, we will push the dictionary as-is into the
        # correct sub-document of the scan definition.
        if 'compliance' in kw:
            scan['audits'] = self._check('compliance', kw['compliance'], dict)
            del (kw['compliance'])

        if 'plugins' in kw:
            scan['plugins'] = self._check('plugins', kw['plugins'], dict)
            del (kw['plugins'])

        # any other remaining keyword arguments will be passed into the settings
        # sub-document.  The bulk of the data should go here...
        scan['settings'] = dict_merge(scan['settings'], kw)
        return scan
Exemple #18
0
    def details(self, etype, id):
        '''
        Constructs a valid scan document from the specified item.

        .. important::
            Please note that the details method is reverse-engineered from the
            responses from the editor API, and while we are reasonably sure that
            the response should align almost exactly to what the API expects to
            be pushed to it, this method by very nature of what it's doing isn't
            guaranteed to always work.

        Args:
            etype (str): The type of object to request.
            scan_id (int): The unique identifier for the scan.

        Returns:
            :obj:`dict`:
                The constructed scan configuration resource.

        Examples:
            >>> policy = tio.editor.details('scan', 1)
            >>> pprint(scan)
        '''

        # Get the editor object
        editor = self.obj_details(etype, id)

        # define the initial skeleton of the scan object
        obj = {
            'settings': policy_settings(editor['settings']),
            'uuid': editor['uuid']
        }

        # graft on the basic settings that aren't stored in any input sections.
        for item in editor['settings']['basic']['groups']:
            for setting in item.keys():
                if setting not in ['name', 'title', 'inputs', 'sections']:
                    obj['settings'][setting] = item[setting]

        if 'credentials' in editor:
            # if the credentials sub-document exists, then lets walk down the
            # credentials dataset
            obj['credentials'] = {
                'current': self._api.editor.parse_creds(
                    editor['credentials']['data'])
            }

            # We also need to gather the settings from the various credential
            # settings that are unique to the scan.
            for ctype in editor['credentials']['data']:
                for citem in ctype['types']:
                    if 'settings' in citem and citem['settings']:
                        obj['settings'] = dict_merge(
                            obj['settings'], policy_settings(
                                citem['settings']))

        if 'compliance' in editor:
            # if the audits sub-document exists, then lets walk down the
            # audits dataset.
            obj['compliance'] = {
                'current': self._api.editor.parse_audits(
                    editor['compliance']['data'])
            }

            # We also need to add in the "compliance" settings into the scan
            # settings.
            for item in editor['compliance']['data']:
                if 'settings' in item:
                    obj['settings'] = dict_merge(
                        obj['settings'], policy_settings(
                            item['settings']))

        if 'plugins' in editor:
            # if the plugins sub-document exists, then lets walk down the
            # plugins dataset.
            obj['plugins'] = self._api.editor.parse_plugins(
                etype, editor['plugins']['families'], id)

        # We next need to do a little post-parsing of the ACLs to find the
        # owner and put owner_id attribute into the appropriate location.
        if 'acls' in obj['settings'] and isinstance(obj['settings']['acls'], list):
            for acl in obj['settings']['acls']:
                if acl['owner'] == 1:
                    obj['settings']['owner_id'] = acl['id']

        # Clean out the empty attributes for templates:
        if etype == 'scan/policy':
            for key in list(obj['settings'].keys()):
                if obj['settings'][key] == None:
                    del(obj['settings'][key])

        # return the scan document to the caller.
        return obj
Exemple #19
0
    def edit(self,
             tag_value_uuid,
             value=None,
             description=None,
             filters=None,
             filter_type=None,
             all_users_permissions=None,
             current_domain_permissions=None):
        '''
        Updates Tag category/value pair information.

        :devportal:`tag: edit tag value <tags-update-tag-value>`

        Args:
            tag_value_uuid (str):
                The unique identifier for the c/v pair to be edited.
            value (str, optional):
                The new name for the category value.
            description (str, optional):
                New description for the category value.
            filters (list, optional):
                Filters are list of tuples in the form of ('FIELD', 'OPERATOR', 'VALUE').
                Multiple filters can be used and will filter down the data
                for automatically applying the tag to asset.

                Examples::
                    - ``('distro', 'match', ['win', 'linux'])``
                    - ``('name', 'nmatch', 'home')``

                Note that multiple values can be passed in list of string format
            filter_type (str, optional):
                The filter_type operator determines how the filters are combined
                together.  ``and`` will inform the API that all of the filter
                conditions must be met whereas ``or`` would mean that if any of the
                conditions are met. Default is ``and``
            all_users_permissions (list, optional):
                List of the minimum set of permissions all users have on the current tag.
                Possible values are ALL, CAN_EDIT, and CAN_SET_PERMISSIONS.
            current_domain_permissions (list, optional):
                List of user and group-specific permissions for the current tag
                current_domain_permissions are list of tuples in the form of
                ('ID', 'NAME', 'TYPE', 'PERMISSIONS')
                the TYPE can be either `USER` or `GROUP` and
                the PERMISSIONS can be `ALL`, `CAN_EDIT` or `CAN_SET_PERMISSIONS`
                any one or all in list

                Examples::
                    - ``(uuid, '*****@*****.**', 'USER', ['CAN_EDIT'])``

        Returns:
            :obj:`dict`:
                Tag value resource record.

        Examples:
            >>> tio.tags.edit('00000000-0000-0000-0000-000000000000',
            ...     name='NewValueName')
        '''
        payload = dict()
        payload['value'] = self._check('value', value, str)
        if description:
            payload['description'] = self._check('description', description,
                                                 str)

        # get existing values of tag
        current = self.details(
            self._check('tag_value_uuid', tag_value_uuid, 'uuid'))
        current_access_control = current['access_control']

        # created copy of current access control which will be used
        # to compare any changes done in permissions
        access_control = current_access_control.copy()

        # initialize access controls
        payload['access_control'] = dict()

        # Set all users permission
        if all_users_permissions is not None:
            current_access_control['all_users_permissions'] = [
                self._check('i',
                            i,
                            str,
                            choices=['ALL', 'CAN_EDIT', 'CAN_SET_PERMISSIONS'])
                for i in self._check('all_users_permissions',
                                     all_users_permissions,
                                     list,
                                     case='upper')
            ]

        # run current_domain_permissions through permission parser
        if current_domain_permissions is not None:
            current_access_control[
                'current_domain_permissions'] = self._permission_constructor(
                    current_domain_permissions)

        # update payload access control with new values
        payload['access_control'] = dict_merge(payload['access_control'],
                                               current_access_control)

        # We need to pick current value of version if available or set default value to 0
        # this value will be incremented when permissions are updated
        if 'version' in current['access_control']:
            current_version = current['access_control']['version']
        else:
            current_version = 0

        # version value must be incremented each time the permissions are updated
        if not payload['access_control'] == access_control:
            payload['access_control']['version'] = current_version + 1

        # if filters are defined, run the filters through the filter parser...
        # or else apply the filters that are available in current payload
        if filters is not None:
            self._check('filters', filters, list)
            payload['filters'] = self._tag_value_constructor(
                filters, self._api.filters.asset_tag_filters(), filter_type)
        elif 'filters' in current and current['filters']:
            # current value in filters are in form of string.
            # we have to first convert it into dict() form before applying
            current['filters']['asset'] = json.loads(
                current['filters']['asset'])
            payload['filters'] = current['filters']

        return self._api.put('tags/values/{}'.format(
            self._check('tag_value_uuid', tag_value_uuid, 'uuid')),
                             json=payload).json()