diff --git a/.github/workflows/all_green_chack.yml b/.github/workflows/all_green_chack.yml new file mode 100644 index 0000000..74e8f5a --- /dev/null +++ b/.github/workflows/all_green_chack.yml @@ -0,0 +1,41 @@ +--- +name: all_green + +on: + push: # run on every push on folliwing branches + branches: + - main + - stable-1.x + - devel + - dev + # run on all prs + pull_request: + +concurrency: + group: >- + ${{ github.workflow }}-${{ + github.event.pull_request.number || github.sha + }} + cancel-in-progress: true + +jobs: + linters: + uses: ./.github/workflows/linters.yml # use the callable linters job to run tests + sanity: + uses: ./.github/workflows/sanity.yml # use the callable sanity job to run tests + units: + uses: ./.github/workflows/units.yml # use the callable units job to run tests + all_green: + if: ${{ always() }} + needs: + - linters + - sanity + - units + runs-on: ubuntu-latest + steps: + - run: >- + python -c "assert set([ + '${{ needs.linters.result }}', + '${{ needs.sanity.result }}', + '${{ needs.units.result }}' + ]) == {'success'}" \ No newline at end of file diff --git a/.github/workflows/ansible-bot.yml b/.github/workflows/ansible-bot.yml new file mode 100644 index 0000000..13b9f79 --- /dev/null +++ b/.github/workflows/ansible-bot.yml @@ -0,0 +1,17 @@ +--- +name: ansible bot +on: + issues: + types: + - opened + - reopened +jobs: + add_label: + runs-on: ubuntu-latest + permissions: + contents: write + issues: write + steps: + - uses: actions-ecosystem/action-add-labels@v1 + with: + labels: needs_triage \ No newline at end of file diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml new file mode 100644 index 0000000..1b3b896 --- /dev/null +++ b/.github/workflows/integration.yml @@ -0,0 +1,118 @@ +name: Integration tests, dependencies from source +# this workflow is not run in any action + +on: + workflow_dispatch: + +jobs: + integration_source: + env: + PY_COLORS: "1" + source_directory: "./source" + collection_base_dir: "/home/runner/collections" + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ansible-version: + - stable-2.15 + - stable-2.16 + - stable-2.17 + - milestone + - devel + python-version: + - "3.10" + - "3.11" + - "3.12" + exclude: + - ansible-version: stable-2.15 + python-version: "3.12" + continue-on-error: ${{ matrix.ansible-version == 'devel' }} + name: "py${{ matrix.python-version }} / ${{ matrix.ansible-version }}" + steps: + - name: Checkout the collection repository + uses: ansible-network/github_actions/.github/actions/checkout_dependency@main + with: + path: ${{ env.source_directory }} + fetch-depth: "0" + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install ansible-core (${{ matrix.ansible-version }}) + run: | + python3 -m pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible-version }}.tar.gz --disable-pip-version-check + + - name: Pre install collections dependencies first so the collection install does not + if: ${{ inputs.collection_pre_install != '' }} + run: | + ansible-galaxy collection install --pre ${{ inputs.collection_pre_install }} -p ${{ env.collection_base_dir }} + + - name: Read collection metadata from galaxy.yml + id: identify + uses: ansible-network/github_actions/.github/actions/identify_collection@main + with: + source_path: ${{ env.source_directory }} + + - name: Build and install the collection + uses: ansible-network/github_actions/.github/actions/build_install_collection@main + with: + install_python_dependencies: true + source_path: ${{ env.source_directory }} + collection_path: ${{ steps.identify.outputs.collection_path }} + tar_file: ${{ steps.identify.outputs.tar_file }} + ansible_version: ${{ matrix.ansible-version }} + + - name: Print the ansible version + run: ansible --version + + - name: Print the python dependencies + run: python3 -m pip list + + - name: Create integration_config.yml + run: | + cd ${{ steps.identify.outputs.collection_path }}/tests/integration + cat < integration_config.yml + tenable_access_key: ${{ secrets.TENABLE_ACCESS_KEY }} + tenable_secret_key: ${{ secrets.TENABLE_SECRET_KEY }} + EOF + + - name: Run api integration tests and excluding the ones that depend on assets + run: | + . /tmp/venv_${{ matrix.python-version }}_${{ matrix.ansible-version.replace('.', '_') }}/bin/activate + excludeList=( + "add_agent_to_group/" + "create_report/" + "get_agent_details/" + "get_asset_activity_log/" + "get_asset_information/" + "get_asset_vulnerability_details/" + "get_report_status/" + "list_agents_by_group/" + "list_asset_vulnerabilities/" + "list_asset_vulnerabilities_for_plugin/" + "list_tags_for_an_asset/" + "rename_agent/" + "update_agent_group_name/" + "upload_file/" + "get_scanner_details/" + "launch_scan/" + "list_agents/" + "stop_scan/" + "update_scan/" + "add_or_remove_asset_tags/" + "get_asset_details/" + "create_network/" + "delete_network/" + "get_network_asset_count/" + "get_network_details/" + "list_networks/" + "list_network_scanners/" + "list_assignable_scanners/" + "update_network/" + ) + excludeArgs=$(printf " --exclude %s" "${excludeList[@]}") + ansible-test integration $excludeArgs -v + working-directory: ${{ steps.identify.outputs.collection_path }} diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml new file mode 100644 index 0000000..5d614b9 --- /dev/null +++ b/.github/workflows/linters.yml @@ -0,0 +1,13 @@ +name: CI for Linter Checks using tox, isort, flake, ansible-lint + + +on: [workflow_call] # allow this workflow to be called from other workflows + + +jobs: + linters: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + uses: ansible-network/github_actions/.github/workflows/tox.yml@main + with: + envname: "" + labelname: "lint" diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml new file mode 100644 index 0000000..c459031 --- /dev/null +++ b/.github/workflows/sanity.yml @@ -0,0 +1,8 @@ +name: CI for Sanity Checks of ansible-test sanity + +on: [workflow_call] # allow this workflow to be called from other workflows + +jobs: + sanity: + if: "!contains(github.event.head_commit.message, '[skip ci]')" + uses: ansible-network/github_actions/.github/workflows/sanity.yml@main diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml new file mode 100644 index 0000000..82dcb59 --- /dev/null +++ b/.github/workflows/units.yml @@ -0,0 +1,7 @@ +name: unti tests + +on: [workflow_call] # allow this workflow to be called from other workflows + +jobs: + unit-source: + uses: ansible-network/github_actions/.github/workflows/unit_source.yml@main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22c9d2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +.tox/ +__pycache__/ +tests/output/ +secrets.yml +integration_config.yml +tests/integration/targets/*/vars/main.yml/* +molecule/ +docs/ +dest/ +machines/ +dockerfiles/ +htmlcov/ +containers/ +assets_list.json +assets_tenable.json +agents.json +dest/ +others/ +.coverage +.azure-pipelines/Dockerfile +.azure-pipelines/dockerfile_simple +networks +assets +agents.json +assets_list2.json +assets_list.json +valkiriaaquatica-tenable-[0-9]*.tar.gz +MANIFEST.json +inventario.json +scripts_dev/ +tox-ansible.ini +review_tox +test_tfg/ \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..e69de29 diff --git a/CI.md b/CI.md new file mode 100644 index 0000000..098b7c0 --- /dev/null +++ b/CI.md @@ -0,0 +1,11 @@ +# CI + +## Tenable Nessus Agent Collections + +GitHub Actions are used to run the Continuous Integration for valkiriaaquatica.tenable collection. The workflows used for the CI can be found in the /.github/workflow/ directory. These workflows include jobs to run the integration tests, sanity tests, linters, check and doc related checks. The following table lists the python and ansible versions against which these jobs are run. + +| Jobs | Description | Python Versions | Ansible Versions | +| ------ |-------| ------ | -----------| +| Linters | Runs 'black', 'flake8','isort','ansible-linter' on plugins and tests +| Unit tests | Executes the unit test cases | 3.9, 3.10, 3.11.0, 3.12.0 | Stable-2.15+ | +| Integration tests | Executes the integration test suites. To run them, it is necessary to adjust in the integration_config.yml file the credentials or if it is run on GitHub actions defined them as secret variables | 3.9, 3.10, 3.11.0, 3.12.0 | Stable-2.15+ | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..47c6e72 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +# Contributing + +## Tenable Nessus Agent Collection + + +### valkiriaaquatica.tenable +Contains most of the interactions that can be donde with Tenable.IO API usefull for the nessus agent. + + +## Submitting Issues + +For any new module idea, code review, change or whatever please pr on this repository on a new patch branch. +Also any issue can be open to improve, delete, change or any idea sbmitted on any part of the collection. + +## Writing New Code and Testing (recommended but not mandatory) + +Please follow the Ansible Documentation on [these instructions](https://docs.ansible.com/ansible/latest/community/create_pr_quick_start.html) +for developing the new code for the collection. +Also any Documentation can be change or improve. + +It is recommended to execute this on a python virtual enviroment. +1. Create the virtual enviroment (recommended) +``` +python3 -m venv env +``` +2. Install testing requirements +``` +pip install -r test-requirements.txt +``` +3. Write your code and your tests (recommended but not mandatory) + +- Sanity tests: +``` +ansible-test sanity --docker -v +``` + +- Unit tests: +``` +ansible-test unit --docker -v +``` + +- Integration tests: +``` +ansible-test integration name_of_the_test_module --docker -v +``` + +If you get stuck with any of this tests, when the PR is sbmitted it will automatically test for sanity and unit tests. + +## Check for pending Tests to be done +In the collection directory there is the check_pending_tests.sh file. +Follow this steps to output the pending and done tests. +``` +chmod +x check_pending_tests.sh +``` + +``` +./check_pending_tests.sh +``` + +## Blessed Contributions on Pending Developments +- Tests: not all modules have integration tests written, feel free to try. +- Not all plugins/modules have the RETURN specified, feel free to complete. +- Docs: there are nod docs written for reading the collection outside GitHub, feel free to help documenting. +- Recheck CI: i'm working on a recheck option to actual execute all_green CI when a recheck or RECHECK is written on a pull request. + (As a Jenkins user GitHub actions is a bit new for me) +- Help a shared library for Jenkins to implement the actual GitHub actions like sanity, units or linter but with Jenkins. + +## More information about contributing + +General information about setting up your Python environment, testing modules, +Ansible coding styles, and more can be found in the [Ansible Community Guide]( +https://docs.ansible.com/ansible/latest/community/index.html). + + +For general information on running the integration tests see +[this page](https://docs.ansible.com/ansible/latest/community/collection_contributors/test_index.html) and +[Integration Tests page of the Module Development Guide](https://docs.ansible.com/ansible/devel/dev_guide/testing_integration.html#non-destructive-tests). +Ignore the part about `source hacking/env-setup`. That's only applicable for working on `ansible-core`. +You should be able to use the `ansible-test` that's installed with Ansible generally. +Look at [the section on configuration for cloud tests](https://docs.ansible.com/ansible/devel/dev_guide/testing_integration.html#other-configuration-for-cloud-tests). + +- [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html) - Details on contributing to Ansible +- [Contributing to Collections](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections) - How to check out collection git repositories correctly + + + +### Communication +At the moment there is no communication channel, I'm sorry for that :( diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..20d40b6 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..0e08d2b --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,311 @@ +// this file when collection is public made will not be present. tis is also a PR test +pipeline { + agent none + stages { + stage('Matrix Build') { + matrix { + axes { + axis { + name 'PYTHON_VERSION' + values '3.9.0', '3.10.0', '3.11.0', '3.12.0' + } + axis { + name 'ANSIBLE_VERSION' + values 'stable-2.15', 'stable-2.16', 'stable-2.17', 'devel', 'milestone' + } + } + stages { + stage('Check Exclusions and Setup Environment') { + agent { + docker { + image 'fermendy/tenable_dependencias:latest' + args '-u root:root' + } + } + environment { + EXCLUSIONS = "3.9.0:stable-2.16,3.9.0:stable-2.17,3.9.0:devel,3.9.0:milestone,3.12.0:stable-2.15,3.12.0:milestone" + } + stages { + stage('Check Exclusions') { + steps { + script { + def exclusions = EXCLUSIONS.split(",").collect { it.trim() } + def currentCombination = "${PYTHON_VERSION}:${ANSIBLE_VERSION}" + echo "Verifying exclusion for combination: ${currentCombination}" + if (exclusions.any { it == currentCombination }) { + echo "Skipping detected not posible combination: Python ${PYTHON_VERSION} y Ansible ${ANSIBLE_VERSION}" + error "Exclusion detected. Stopping the build for this combination." + } + } + } + } + stage('Setup Environment') { + steps { + echo "Setting up environment for Python ${PYTHON_VERSION} and Ansible ${ANSIBLE_VERSION}" + } + } + stage('Clean Previous Builds') { + steps { + sh ''' + find ${WORKSPACE} -name 'valkiriaaquatica-tenable-*.tar.gz' -exec rm {} \\; || true + find ${WORKSPACE} -name 'venv_*' -exec rm -rf {} \\; || true + ''' + } + } + stage('Clean Workspace and Checkout') { + steps { + sh 'rm -rf *' + checkout scm + sh 'ls -la' + } + } + stage('Install System Dependencies and Pyenv') { + steps { + script { + installPyenv("${PYTHON_VERSION}") + } + } + } + stage('Setup Ansible and Virtualenv') { + steps { + script { + setupAnsibleEnv("${PYTHON_VERSION}", "${ANSIBLE_VERSION}") + } + } + } + stage('Extract Galaxy Metadata') { + steps { + script { + extractGalaxyMetadata() + } + } + } + stage('Build and Install Collection') { + steps { + script { + buildAndInstallCollection("${PYTHON_VERSION}", "${ANSIBLE_VERSION}") + } + } + } + stage('Run Tox Ansible-Lint Testing') { + steps { + script { + runToxTesting("${PYTHON_VERSION}", "${ANSIBLE_VERSION}") + } + } + } + stage('Run Ansible Sanity Test') { + steps { + script { + runAnsibleSanityTest("${PYTHON_VERSION}") + } + } + } + stage('Run Ansible Unit Tests') { + steps { + script { + runAnsibleUnitTest("${PYTHON_VERSION}") + } + } + post { + always { + junit 'results.xml' + } + } + } + stage('Run Ansible Integration Test') { + steps { + script { + runAnsibleIntegrationTest("${PYTHON_VERSION}") + } + } + } + } + } + } + } + } + } + post { + always { + cleanWorkspace() + } + } +} + +def installPyenv(pythonVersion) { + sh """ + curl https://pyenv.run | bash + echo 'export PYENV_ROOT="\$HOME/.pyenv"' >> ~/.bashrc + echo 'export PATH="\$PYENV_ROOT/bin:\$PATH"' >> ~/.bashrc + echo 'eval "\$(pyenv init --path)"' >> ~/.bashrc + echo 'eval "\$(pyenv init -)"' >> ~/.bashrc + echo 'eval "\$(pyenv virtualenv-init -)"' >> ~/.bashrc + export PYENV_ROOT="\$HOME/.pyenv" + export PATH="\$PYENV_ROOT/bin:\$PATH" + eval "\$(pyenv init --path)" + eval "\$(pyenv init -)" + eval "\$(pyenv virtualenv-init -)" + pyenv install -s ${pythonVersion} + pyenv global ${pythonVersion} + python -m pip install --upgrade pip + python --version + """ +} + +def setupAnsibleEnv(pythonVersion, ansibleVersion) { + def venvName = "/tmp/venv_${pythonVersion}_${ansibleVersion.replace('.', '_')}" + sh """ + export PYENV_ROOT="/root/.pyenv" + export PATH="\$PYENV_ROOT/bin:\$PATH" + eval "\$(pyenv init --path)" + eval "\$(pyenv init -)" + eval "\$(pyenv virtualenv-init -)" + pyenv global ${pythonVersion} + python -m venv ${venvName} + . ${venvName}/bin/activate + python -m pip install https://github.com/ansible/ansible/archive/${ansibleVersion}.tar.gz + pip install -r test-requirements.txt + apt-get install jq -y + pip install yq + """ +} + + +def extractGalaxyMetadata() { + def venvName = "/tmp/venv_${env.PYTHON_VERSION}_${env.ANSIBLE_VERSION.replace('.', '_')}" + def envVars = sh(script: """ + . ${venvName}/bin/activate + NAMESPACE=\$(yq -r '.namespace' galaxy.yml) + COLLECTION_NAME=\$(yq -r '.name' galaxy.yml) + VERSION=\$(yq -r '.version' galaxy.yml) + echo "NAMESPACE=\$NAMESPACE" + echo "COLLECTION_NAME=\$COLLECTION_NAME" + echo "VERSION=\$VERSION" + echo "NAMESPACE=\$NAMESPACE" > \$WORKSPACE/metadata.env + echo "COLLECTION_NAME=\$COLLECTION_NAME" >> \$WORKSPACE/metadata.env + echo "VERSION=\$VERSION" >> \$WORKSPACE/metadata.env + cat \$WORKSPACE/metadata.env + """, returnStdout: true).trim() + + def props = readFile("${env.WORKSPACE}/metadata.env").split('\n') + env.NAMESPACE = props[0].split('=')[1].trim() + env.COLLECTION_NAME = props[1].split('=')[1].trim() + env.VERSION = props[2].split('=')[1].trim() + + echo "NAMESPACE=${env.NAMESPACE}" + echo "COLLECTION_NAME=${env.COLLECTION_NAME}" + echo "VERSION=${env.VERSION}" +} + +def buildAndInstallCollection(pythonVersion, ansibleVersion) { + def venvName = "/tmp/venv_${pythonVersion}_${ansibleVersion.replace('.', '_')}" + def tarFileName = "${env.NAMESPACE}-${env.COLLECTION_NAME}-${env.VERSION}.tar.gz" + sh """ + export PYENV_ROOT="\$HOME/.pyenv" + export PATH="\$PYENV_ROOT/bin:\$PATH" + eval "\$(pyenv init --path)" + eval "\$(pyenv init -)" + eval "\$(pyenv virtualenv-init -)" + pyenv global ${pythonVersion} + . ${venvName}/bin/activate + ansible-galaxy collection build -vvv + tar -tzf ${tarFileName} + ansible-galaxy collection install ${tarFileName} -vvv + cp galaxy.yml /root/.ansible/collections/ansible_collections/valkiriaaquatica/tenable/galaxy.yml + """ +} + + +def runToxTesting(pythonVersion, ansibleVersion) { + def venvName = "/tmp/venv_${pythonVersion}_${ansibleVersion.replace('.', '_')}" + sh """ + . ${venvName}/bin/activate + cd /root/.ansible/collections/ansible_collections/valkiriaaquatica/tenable/ + rm -rf MANIFEST.json + ls -la + tox -m lint -vv --skip-missing-interpreters=false + rm -rf .tox + """ +} + +def runAnsibleSanityTest(pythonVersion) { + def venvName = "/tmp/venv_${pythonVersion}_${env.ANSIBLE_VERSION.replace('.', '_')}" + def shortPythonVersion = pythonVersion.replaceAll(/\\.\\d+$/, '') + sh """ + . ${venvName}/bin/activateS + echo "Using Python version: ${shortPythonVersion}" + cd /root/.ansible/collections/ansible_collections/valkiriaaquatica/tenable/ + ls -la + ansible-test sanity --requirements --color --python default + """ +} + +def runAnsibleUnitTest(pythonVersion) { + def venvName = "/tmp/venv_${pythonVersion}_${env.ANSIBLE_VERSION.replace('.', '_')}" + sh """ + . ${venvName}/bin/activate + ls -la + python -m pytest tests/unit --junitxml=results.xml --showlocals + """ +} + +def runAnsibleIntegrationTest(pythonVersion) { + def venvName = "/tmp/venv_${pythonVersion}_${env.ANSIBLE_VERSION.replace('.', '_')}" + def excludeList = [ + "add_agent_to_group/", + "create_report/", + "get_agent_details/", + "get_asset_activity_log/", + "get_asset_information/", + "get_asset_vulnerability_details/", + "get_report_status/", + "list_agents_by_group/", + "list_asset_vulnerabilities/", + "list_asset_vulnerabilities_for_plugin/", + "list_tags_for_an_asset/", + "rename_agent/", + "update_agent_group_name/", + "upload_file/", + "get_scanner_details/", + "launch_scan/", + "list_agents/", + "stop_scan/", + "update_scan/", + "add_or_remove_asset_tags/", + "get_asset_details/", + "create_network/", + "delete_network/", + "get_network_asset_count/", + "get_network_details/", + "list_networks/", + "list_network_scanners/", + "list_assignable_scanners", + "update_network/", + ] + def excludeArgs = excludeList.collect { "--exclude ${it}" }.join(" ") + sh """ + . ${venvName}/bin/activate + cd tests/integration/ + ls -la + cat < integration_config.yml + tenable_access_key: ${env.TENABLE_ACCESS_KEY} + tenable_secret_key: ${env.TENABLE_SECRET_KEY} + EOF + ls -la + cd ../../.. + ls -la + ansible-test integration ${excludeArgs} -v + """ +} + //ansible-test integration ${excludeArgs} + +def cleanWorkspace() { + node('master_node') { + cleanWs() + sh ''' + find ${WORKSPACE} -name '*.tar.gz' -exec rm {} \\; + find ${WORKSPACE} -name 'venv_*' -exec rm -rf {} \\; + ''' + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..9252dd7 --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +# First of All :) +- This is not developed from a Tenable or RedHat employee, just someone who in his daily work found the necessity to develop it to improve his daily tasks in system administration, devops and security and his teams work also. +- This collection is very usefull for applying quick responses to vulnerbailities, applied devops metodology quickly with secure pipelines and administrate machines that have nessus agent installed. +- That's why maybe you found errors or not "common standard Ansible" ideas, but I tried to follow them :). +- Writing documentation is not my best sorry. +- Testing has been made in Jenkins and in GitHub actions, so if someone wants to help developing Jenkins shared libraries to include in pipelines I will be happy. + +# Ansible collection for Tenable Nessus Agent +[![Doc](https://img.shields.io/badge/docs-latest-brightgreen.svg)](https://docs.ansible.com/ansible/latest/collections/azure/azcollection/index.html) +[![Code of conduct](https://img.shields.io/badge/code%20of%20conduct-Ansible-silver.svg)](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) +[![License](https://img.shields.io/badge/license-GPL%20v3.0-brightgreen.svg)](LICENSE) + +This collection provides a series of Ansible modules and plugins for interact with the Tenable Nessus Agent API. + +https://developer.tenable.com/reference/navigate and installation and linking modules. + +Documentation of individual modules is yet not available as ansible-galaxy official collection. + +## Installation + +It is recommended to run ansible in [Virtualenv](https://virtualenv.pypa.io/en/latest/). + +To install it from Ansible-Galaxy. First Way. +```bash +ansible-galaxy collection install valkiriaaquatica.tenable +``` + +Install dependencies required by the collection (adjust path to collection if necessary): + +```bash +pip3 install -r ~/.ansible/collections/ansible_collections/valkiriaaquatica/tenable/requirements.txt +``` + +To install it from source code of this GitHub repository. Second way. + +```bash +https://github.com/valkiriaaquatica/valkiriaaquatica.tenable_dev.git +``` +Then on the root directory of the collection. +```bash +ansible-galaxy collection build +``` +Then after the valkiriaaquatica.tenable-{version}.tar.gz is created install it. +```bash +ansible-galaxy collection install valkiriaaquatica.tenable-{version}.tar.gz +``` + +## Requirements + +- ansible version >= 2.14 +- Install python dependancies of the requirements.txt file. + + +### Playbooks + +To use a module from Tenable collection, please reference the full namespace, collection name, and modules name that you want to use: + +```yaml +--- +- name: Using Tenable collection + hosts: localhost + tasks: + - valkiriaaquatica.tenable.list_assets: + access_key: "your_access_key" + secret_key: "your_secret_key" + filters: + - type: network_id + operator: eq + value: "123456789" +``` + + +### Roles + +For existing Ansible roles, please also reference the full namespace, collection name, and modules name which used in tasks instead of just modules name. + +### Plugins like Inventory + +To use a plugin from Tenable collection, please reference the full namespace, collection name, and plugins name that you want to use: + +```yaml +--- +plugin: valkiriaaquatica.tenable.tenable +full_info: true +include_filters: + - type: "tag.Cloud Provider" + operator: set-has + value: "Google Cloud" +compose: + asset_id_host: "'asset' + id" + hash_id: id | md5 +``` + +## Contributing + +There are many ways in which you can participate in the project, for example: + +- Submit bugs and feature requests, improvements or issues. +- Review source code changes. +- Review the documentation and make pull requests for anything from typos to new content +- If you are interested in fixing issues and contributing directly to the code base, please see the [CONTRIBUTING](CONTRIBUTING.md) document +- Check "Blessed Contributions on Pending Developments" in the file [CONTRIBUTING](CONTRIBUTING.md) to see ideas to help dn develop + +## License + +GNU General Public License v3.0 + +See [LICENSE](LICENSE) to see the full text. + + + +# Just for own development issues +ansible-test integration --exclude add_agent_to_group --exclude create_report --exclude get_agent_details --exclude get_asset_activity_log --exclude get_asset_information --exclude get_asset_vulnerability_details --exclude get_report_status --exclude list_agents_by_group --exclude list_asset_vulnerabilities --exclude list_asset_vulnerabilities_for_plugin --exclude list_tags_for_an_asset --exclude rename_agent --exclude update_agent_group_name --exclude upload_file --exclude get_scanner_details --exclude launch_scan --exclude list_agents --exclude stop_scan --exclude update_scan --exclude add_or_remove_asset_tags --exclude get_asset_details --exclude create_network --exclude delete_network --exclude get_network_details --exclude list_networks --exclude update_network --exclude list_assignable_scanners --exclude move_assets --docker -v diff --git a/check_pending_tests.sh b/check_pending_tests.sh new file mode 100755 index 0000000..65ad337 --- /dev/null +++ b/check_pending_tests.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +### this script outputs the pending unit and integration tests that remain of the actual modules ### + +COLLECTION_ROOT="$(pwd)" +MODULES_DIR="$COLLECTION_ROOT/plugins/modules" +UNIT_TESTS_DIR="$COLLECTION_ROOT/tests/unit/plugins/modules" +INTEGRATION_TESTS_DIR="$COLLECTION_ROOT/tests/integration/targets" + +if [[ ! -d "$MODULES_DIR" || ! -d "$UNIT_TESTS_DIR" || ! -d "$INTEGRATION_TESTS_DIR" ]]; then + echo "Not a valid directory. Please be on the root collection directory." + exit 1 +fi +for module_file in "$MODULES_DIR"/*.py; do + module_name=$(basename "$module_file" .py) + # unit + unit_test_file="$UNIT_TESTS_DIR/test_${module_name}.py" + if [[ -f "$unit_test_file" ]]; then + unit_test_status="present" + else + unit_test_status="not present" + fi + + # integration + integration_test_dir="$INTEGRATION_TESTS_DIR/$module_name" + if [[ -d "$integration_test_dir" ]]; then + integration_test_status="present" + else + integration_test_status="not present" + fi + # prints + echo "Module: $module_name" + echo " Unit test: $unit_test_status" + echo " Integration test: $integration_test_status" +done diff --git a/galaxy.yml b/galaxy.yml new file mode 100644 index 0000000..b5899a6 --- /dev/null +++ b/galaxy.yml @@ -0,0 +1,12 @@ +namespace: valkiriaaquatica +name: tenable +version: 0.0.1 +readme: README.md +authors: +- fernandomendietaovejero@gmail.com +description: Tenable Nessus Agent API collection for sysops , devops and secops. +license_file: COPYING +repository: https://github.com/valkiriaaquatica/valkiriaaquatica.tenable.git +# documentation: not yet +homepage: https://github.com/valkiriaaquatica/valkiriaaquatica.tenable.git +issues: https://github.com/valkiriaaquatica/valkiriaaquatica.tenable/issues diff --git a/meta/runtime.yml b/meta/runtime.yml new file mode 100644 index 0000000..e42c3fb --- /dev/null +++ b/meta/runtime.yml @@ -0,0 +1 @@ +requires_ansible: '>=2.14' diff --git a/plugins/doc_fragments/agent.py b/plugins/doc_fragments/agent.py new file mode 100644 index 0000000..55fdfb0 --- /dev/null +++ b/plugins/doc_fragments/agent.py @@ -0,0 +1,20 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r""" +options: + agent_id: + description: + - ID of the agent to manage. + - Use the list_agents module to retrieve all the agents. + required: true + type: str +""" diff --git a/plugins/doc_fragments/agent_group.py b/plugins/doc_fragments/agent_group.py new file mode 100644 index 0000000..5862062 --- /dev/null +++ b/plugins/doc_fragments/agent_group.py @@ -0,0 +1,20 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r""" +options: + group_id: + description: + - ID of the group. + - Use the list_agent_groups module to list all the groups. + required: true + type: str +""" diff --git a/plugins/doc_fragments/asset.py b/plugins/doc_fragments/asset.py new file mode 100644 index 0000000..5b33f07 --- /dev/null +++ b/plugins/doc_fragments/asset.py @@ -0,0 +1,20 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r""" +options: + asset_uuid: + description: + - The UUID of the asset. + - Check for the UUID using the module list_assets. + required: true + type: str +""" diff --git a/plugins/doc_fragments/attributes.py b/plugins/doc_fragments/attributes.py new file mode 100644 index 0000000..e285351 --- /dev/null +++ b/plugins/doc_fragments/attributes.py @@ -0,0 +1,20 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r""" +options: + attribute_id: + description: + - The UUID of the attribute. + - Check for the UUID using the module list_attributes module. + required: true + type: str +""" diff --git a/plugins/doc_fragments/bulk.py b/plugins/doc_fragments/bulk.py new file mode 100644 index 0000000..74db93b --- /dev/null +++ b/plugins/doc_fragments/bulk.py @@ -0,0 +1,56 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r""" +options: + criteria: + description: + - Specifies the criteria you wish to filter agents on. + - The criteria is used to narrow down the list of agents to perform the specified action on. + type: dict + required: false + suboptions: + all_agents: + description: + - Indicates whether or not to match against all agents. + type: bool + wildcard: + description: + - A string used to match against all string-like attributes of an agent. + type: str + filters: + description: + - An array of string or numeric operations to match against agents. For example, name:match:laptop or core_version:lt:10.0.0. + type: list + elements: str + filter_type: + description: + - Indicates how to combine the filters conditions. Possible values are and or or. + type: str + choices: ["and", "or"] + hardcoded_filters: + description: + - Additional filters that will always be added as and conditions. + type: list + elements: str + items: + description: + - An array of agent IDs or agent UUIDs to add to the criteria filter. + type: list + elements: str + required: false + not_items: + description: + - An array of agent IDs or agent UUIDs to exclude from the criteria filter. + type: list + elements: str + required: false +""" diff --git a/plugins/doc_fragments/category.py b/plugins/doc_fragments/category.py new file mode 100644 index 0000000..96b1c83 --- /dev/null +++ b/plugins/doc_fragments/category.py @@ -0,0 +1,21 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +class ModuleDocFragment(object): + DOCUMENTATION = r""" +options: + category_uuid: + description: + - The UUID of the tag category to return details for. + - To list the t dict: + url = f"{self.base_url}/{endpoint}" + if params: + query_string = urlencode(params, doseq=True) + url += f"?{query_string}" + + if data: + data = json.dumps(data).encode("utf-8") + if method in ["PATCH", "POST", "PUT"]: + self.headers["Content-Type"] = "application/json" + else: + self.headers.pop("Content-Type", None) + + try: + response = self.client.open(method=method, url=url, headers=self.headers, data=data) + response_body = response.read() + if response_body: + return {"status_code": response.getcode(), "data": json.loads(response_body.decode("utf-8"))} + else: + return {"status_code": response.getcode(), "data": {}} + except HTTPError as e: + error_data = e.read().decode("utf-8") + if e.code == 400: + raise BadRequestError(error_data, e.code) + elif e.code == 401: + raise AuthenticationError("Authentication failure. Please check your API keys.", e.code) + else: + raise UnexpectedAPIResponse(e.code, error_data) + except URLError as e: + raise TenableAPIError(f"URL error occurred: {str(e)}", None) + except TimeoutError as e: + raise TenableAPIError(f"Request timed out: {str(e)}", None) + + def upload_file(self, endpoint: str, file_path: str) -> dict: + url = f"{self.base_url}/{endpoint}" + file_name = os.path.basename(file_path) + content_type, unused_mime_type = mimetypes.guess_type(file_path) + if content_type is None: + content_type = "application/octet-stream" + + boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW" + self.headers["Content-Type"] = f"multipart/form-data; boundary={boundary}" + data = [] + data.append(f"--{boundary}") + data.append(f'Content-Disposition: form-data; name="Filedata"; filename="{file_name}"') + data.append(f"Content-Type: {content_type}") + data.append("") + + with open(file_path, "rb") as f: + data.append(f.read()) + + data.append(f"--{boundary}--") + data.append("") + data = "\r\n".join(data).encode("utf-8") + + try: + response = self.client.open(method="POST", url=url, data=data, headers=self.headers) + response_body = response.read().decode("utf-8") + return {"status_code": response.getcode(), "data": json.loads(response_body) if response_body else {}} + except Exception as e: + if self.module: + self.module.fail_json(msg=str(e)) + else: + raise TenableAPIError(f"Failed to upload file: {str(e)}", None) + + +def init_tenable_api(module=None, access_key=None, secret_key=None): + return TenableAPI(module=module, access_key=access_key, secret_key=secret_key) diff --git a/plugins/module_utils/arguments.py b/plugins/module_utils/arguments.py new file mode 100644 index 0000000..b9e49f9 --- /dev/null +++ b/plugins/module_utils/arguments.py @@ -0,0 +1,359 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from ansible.module_utils.basic import env_fallback + +ARG_SPECS = { + "access_key": { + "type": "str", + "required": False, + "no_log": True, + "fallback": (env_fallback, ["TENABLE_ACCESS_KEY"]), + }, + "secret_key": { + "type": "str", + "required": False, + "no_log": True, + "fallback": (env_fallback, ["TENABLE_SECRET_KEY"]), + }, + "type": {"type": "str", "required": True, "choices": ["scan", "policy", "remediation"]}, + "value_uuid": {"type": "str", "required": True}, + "value": {"type": "str", "required": False}, + "user_id": {"type": "str", "required": True}, + "history_id": {"type": "str", "required": False}, + "plugin_id": {"type": "str", "required": True}, + "scanner_id": {"type": "int", "required": True}, + "wizard_uuid": {"type": "str", "required": True}, + "name": {"type": "str", "required": True}, + "credential_uuid": {"required": True, "type": "str"}, + "scan_id": {"type": "str", "required": True}, + "exclusion_id": {"type": "int", "required": True}, + "asset_uuid": {"type": "str", "required": True}, + "attribute_id": {"type": "str", "required": True}, + "folder_id": {"type": "int", "required": False}, + "last_modification_date": {"type": "int", "required": False}, + "group_id": {"type": "str", "required": True}, + "agent_id": {"type": "str", "required": True}, + "limit": {"type": "int", "required": False}, + "offset": {"type": "int", "required": False}, + "filters": {"type": "list", "elements": "dict", "required": False}, + "sort": {"type": "str", "required": False}, + "date_range": {"type": "int", "required": False}, + "filter_search_type": {"type": "str", "choices": ["and", "or"], "required": False}, + "filter_type": {"type": "str", "choices": ["and", "or"], "required": False}, + "wildcard": {"type": "str", "required": False}, + "wildcard_text": {"type": "str", "required": False}, + "wildcard_fields": {"type": "list", "elements": "str", "required": False}, + "last_updated": {"type": "str", "required": False}, + "page": {"type": "int", "required": False}, + "size": {"type": "int", "required": False}, + "assets_ttl_days": {"type": "int", "required": False}, + "schedule": { + "type": "dict", + "required": False, + "options": { + "enabled": {"type": "bool"}, + "starttime": {"type": "str", "required": False}, + "endtime": {"type": "str", "required": False}, + "timezone": {"type": "str", "required": False}, + "rrules": { + "type": "dict", + "required": False, + "options": { + "freq": {"type": "str", "required": False}, + "interval": {"type": "int", "required": False}, + "byweekday": {"type": "str", "required": False}, + "bymonthday": {"type": "int", "required": False}, + }, + }, + }, + }, + "criteria": { + "required": False, + "type": "dict", + "options": { + "all_agents": {"required": False, "type": "bool"}, + "wildcard": {"required": False, "type": "str"}, + "filters": {"required": False, "type": "list", "elements": "str"}, + "filter_type": {"required": False, "type": "str", "choices": ["and", "or"]}, + "hardcoded_filters": {"required": False, "type": "list", "elements": "str"}, + }, + }, + "items": {"required": False, "type": "list", "elements": "str"}, + "not_items": {"required": False, "type": "list", "elements": "str"}, + "directive": { + "required": True, + "type": "dict", + "options": { + "type": {"required": True, "type": "str", "choices": ["restart", "settings"]}, + "options": { + "required": True, + "type": "dict", + "options": { + "hard": {"required": False, "type": "bool"}, + "idle": {"required": False, "type": "bool"}, + "settings": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "setting": {"required": True, "type": "str"}, + "value": {"required": True, "type": "str"}, + }, + }, + }, + }, + }, + }, + "network_id": {"type": "str", "required": False}, + "network_uuid": {"type": "str", "required": True}, + "alt_targets": {"type": "list", "elements": "str", "required": False}, + "rollover": {"type": "bool", "required": False}, + "all_fields": {"type": "str", "required": False, "choices": ["default", "full"]}, + "age": {"type": "int", "required": False}, + "authenticated": {"type": "bool", "required": False}, + "exploitable": {"type": "bool", "required": False}, + "resolvable": {"type": "bool", "required": False}, + "severity": {"type": "string", "choices": ["critical", "high", "medium", "low", "info"], "required": False}, + "uuid": {"type": "str", "required": True}, + "settings": { + "type": "dict", + "required": True, + "options": { + "name": {"type": "str", "required": True}, + "description": {"type": "str", "required": False}, + "policy_id": {"type": "int", "required": False}, + "folder_id": {"type": "int", "required": False}, + "scanner_id": {"type": "str", "required": False}, + "target_network_uuid": {"type": "str", "required": False}, + "enabled": {"type": "bool", "required": False}, + "launch": { + "type": "str", + "choices": ["ON_DEMAND", "DAILY", "WEEKLY", "MONTHLY", "YEARLY"], + "required": False, + }, + "scan_time_window": {"type": "int", "required": False}, + "starttime": {"type": "str", "required": False}, + "rrules": {"type": "str", "required": False}, + "timezone": {"type": "str", "required": False}, + "text_targets": {"type": "str", "required": False}, + "target_groups": {"type": "list", "elements": "int", "required": False}, + "file_targets": {"type": "str", "required": False}, + "tag_targets": {"type": "list", "elements": "str", "required": False}, + "host_tagging": {"type": "str", "required": False}, + "agent_group_id": {"type": "list", "elements": "str", "required": False}, + "agent_scan_launch_type": {"type": "str", "required": False, "choices": ["scheduled", "triggered"]}, + "triggers": { + "type": "list", + "elements": "dict", + "options": { + "type": {"type": "str", "required": False, "choices": ["periodic", "file-exists"]}, + "options": { + "type": "dict", + "options": { + "periodic_hourly_interval": { + "type": "int", + "required": False, + }, + "filename": {"type": "str", "required": False}, + }, + }, + }, + }, + "refresh_reporting_type": {"type": "str", "required": False, "choices": ["scans", "days"]}, + "refresh_reporting_frequency_scans": {"type": "int", "required": False}, + "refresh_reporting_frequency_days": {"type": "int", "required": False}, + "disable_refresh_reporting": {"type": "str", "required": False, "choices": ["yes", "no"]}, + "emails": {"type": "str", "required": False}, + "acls": { + "type": "list", + "elements": "dict", + "options": { + "permissions": { + "type": "int", + "required": False, + }, + "owner": { + "type": "int", + "required": False, + }, + "display_name": { + "type": "str", + "required": False, + }, + "name": { + "type": "str", + "required": False, + }, + "id": { + "type": "int", + "required": False, + }, + "type": { + "type": "str", + "required": False, + "choices": ["default", "user", "group"], + }, + }, + }, + }, + }, + "credentials": { + "required": False, + "type": "dict", + "options": { + "add": { + "required": False, + "type": "dict", + "options": { + "Host": { + "required": False, + "type": "dict", + "options": { + "Windows": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "domain": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "auth_method": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "no_log": True}, + }, + } + }, + } + }, + } + }, + }, + "plugin_configurations": { + "type": "list", + "required": False, + "elements": "dict", + "options": { + "plugin_family_name": {"type": "str", "required": True}, + "plugins": { + "type": "list", + "required": True, + "elements": "dict", + "options": { + "plugin_id": {"type": "str", "required": True}, + "status": {"type": "str", "required": True, "choices": ["enabled", "disabled"]}, + }, + }, + }, + }, + "category_name": {"type": "str", "required": False}, + "category_uuid": {"type": "str", "required": False}, + "category_description": {"type": "str", "required": False}, + "description": {"type": "str", "required": False}, + "access_control": { + "type": "dict", + "options": { + "current_user_permissions": {"type": "list", "elements": "str", "required": False}, + "defined_domain_permissions": {"type": "list", "elements": "str", "required": False}, + "all_users_permissions": {"type": "list", "elements": "str", "required": False}, + "current_domain_permissions": { + "type": "list", + "elements": "dict", + "options": { + "id": {"type": "str", "required": False}, + "name": {"type": "str", "required": False}, + "type": {"type": "str", "choices": ["USER", "GROUP", "ROLE"], "required": False}, + "permissions": {"type": "list", "elements": "str", "required": False}, + }, + "required": False, + }, + "version": {"type": "int", "required": False}, + }, + }, + "filters_tags": { + "type": "dict", + "options": { + "asset": { + "type": "dict", + "options": { + "and": { + "type": "list", + "elements": "dict", + "options": { + "field": {"type": "str", "required": False}, + "operator": {"type": "str", "required": False}, + "value": {"type": "str", "required": False}, + }, + "required": False, + }, + "or": { + "type": "list", + "elements": "dict", + "options": { + "field": {"type": "str", "required": False}, + "operator": {"type": "str", "required": False}, + "value": {"type": "str", "required": False}, + }, + "required": False, + }, + }, + } + }, + }, +} + + +REPEATED_SPECIAL_ARGS = { + "filters": { + "type": "dict", + "options": { + "asset": { + "type": "dict", + "options": { + "and": { + "type": "list", + "elements": "dict", + "options": { + "field": {"type": "str", "required": False}, + "operator": {"type": "str", "required": False}, + "value": {"type": "str", "required": False}, + }, + "required": False, + }, + "or": { + "type": "list", + "elements": "dict", + "options": { + "field": {"type": "str", "required": False}, + "operator": {"type": "str", "required": False}, + "value": {"type": "str", "required": False}, + }, + "required": False, + }, + }, + } + }, + }, + "value": {"type": "str", "required": True}, + "name": {"type": "str", "required": False}, + "network_id": {"type": "str", "required": True}, + "folder_id": {"type": "int", "required": True}, +} + + +def get_spec(*param_names): + """Extracts the specific necessary parameters.""" + return {name: ARG_SPECS[name] for name in param_names if name in ARG_SPECS} + + +def get_repeated_special_spec(*param_names): + """Extracts the specific necessary parameters that arre repitted from get_spec.""" + spec = {} + for name in param_names: + if name in REPEATED_SPECIAL_ARGS: + spec[name] = REPEATED_SPECIAL_ARGS[name] + return spec diff --git a/plugins/module_utils/exceptions.py b/plugins/module_utils/exceptions.py new file mode 100644 index 0000000..3dd2f9d --- /dev/null +++ b/plugins/module_utils/exceptions.py @@ -0,0 +1,19 @@ +class TenableAPIError(Exception): + def __init__(self, msg=None, status_code=None): + super().__init__(msg) + self.status_code = status_code + + +# error 401 +class AuthenticationError(TenableAPIError): + pass + + +# other errors +class UnexpectedAPIResponse(TenableAPIError): + pass + + +# error 400 +class BadRequestError(TenableAPIError): + pass diff --git a/plugins/module_utils/parameter_actions.py b/plugins/module_utils/parameter_actions.py new file mode 100644 index 0000000..f76e4bd --- /dev/null +++ b/plugins/module_utils/parameter_actions.py @@ -0,0 +1,130 @@ +import urllib.parse +from typing import Any +from typing import Callable +from typing import Dict +from typing import List + + +def build_query_parameters(**kwargs: Any) -> Dict[str, str]: + query_params = {} + param_map = { + "limit": "limit", + "offset": "offset", + "sort": "sort", + "include_deleted": "includeDeleted", + "date_range": "date_range", + "filter_search_type": "filter.search_type", + "filters": "filters", + "wildcard_text": "w", + "wildcard_fields": "wf", + "filter_type": "ft", + "all_fields": "all_fields", + "last_updated": "last_updated", + "size": "size", + "page": "page", + "folder_id": "folder_id", + "last_modification_date": "last_modification_date", + "history_id": "history_id", + "description": "description", + "assets_ttl_days": "assets_ttl_days", + } + + for key, value in kwargs.items(): + if value is not None: + mapped_key = param_map.get(key, key) + if isinstance(value, list): + query_params[mapped_key] = ",".join(value) + else: + query_params[mapped_key] = str(value).lower() if isinstance(value, bool) else str(value) + + return query_params + + +def handle_multiple_filters(query_params: Dict[str, str], filters: List[Dict[str, str]]) -> Dict[str, str]: + if filters: + for idx, filter_ in enumerate(filters): + prefix = f"filter.{idx}" + query_params[f"{prefix}.filter"] = urllib.parse.quote_plus(filter_["type"]) + query_params[f"{prefix}.quality"] = urllib.parse.quote_plus(filter_["operator"]) + query_params[f"{prefix}.value"] = urllib.parse.quote_plus(filter_["value"]) + return query_params + + +def handle_special_filter(query_params: Dict[str, str], filters: List[Dict[str, str]]) -> Dict[str, str]: + if filters: + query_params["f"] = "&".join([f"{f['type']}:{f['operator']}:{f['value']}" for f in filters]) + return query_params + + +def add_custom_filters( + query_params: Dict[str, str], filters: List[Dict[str, str]], filter_handler: Callable +) -> Dict[str, str]: + return filter_handler(query_params, filters) + + +def build_payload(module, keys: List[str]) -> Dict[str, Any]: + payload = {} + + def recurse_build(payload: Dict[str, Any], params: Dict[str, Any], keys: List[str]) -> None: + for key in keys: + value = params.get(key) + if isinstance(value, dict): + sub_payload = {} + recurse_build(sub_payload, value, value.keys()) + if sub_payload: + payload[key] = sub_payload + elif value is not None: + payload[key] = value + + recurse_build(payload, module.params, keys) + return payload + + +def build_complex_payload(params: Dict[str, Any]) -> Dict[str, Any]: + payload = {"uuid": params["uuid"], "settings": {}} + for key, value in params["settings"].items(): + if value is not None: + if isinstance(value, dict): + payload["settings"][key] = {k: v for k, v in value.items() if v is not None} + else: + payload["settings"][key] = value + + if "credentials" in params and params["credentials"]: + payload["credentials"] = {} + for cred_key, cred_value in params["credentials"].items(): + payload["credentials"][cred_key] = {k: v for k, v in cred_value.items() if v is not None} + + if "plugin_configurations" in params and params["plugin_configurations"]: + payload["plugins"] = {} + for plugin_config in params["plugin_configurations"]: + family_name = plugin_config.get("plugin_family_name") + plugins = plugin_config.get("plugins", []) + if family_name and plugins: + if family_name not in payload["plugins"]: + payload["plugins"][family_name] = {"individual": {}} + for plugin in plugins: + plugin_id = plugin.get("plugin_id") + status = plugin.get("status") + if plugin_id and status: + payload["plugins"][family_name]["individual"][plugin_id] = status + + return payload + + +def build_tag_values_payload(params: Dict[str, Any]) -> Dict[str, Any]: + payload = { + "category_name": params.get("category_name"), + "category_uuid": params.get("category_uuid"), + "value": params.get("value"), + } + + if params.get("category_description"): + payload["category_description"] = params["category_description"] + if params.get("description"): + payload["description"] = params["description"] + if params.get("access_control"): + payload["access_control"] = {k: v for k, v in params["access_control"].items() if v is not None} + if params.get("filters"): + payload["filters"] = {k: v for k, v in params["filters"].items() if v is not None} + + return payload diff --git a/plugins/module_utils/simple_requests.py b/plugins/module_utils/simple_requests.py new file mode 100644 index 0000000..71a0545 --- /dev/null +++ b/plugins/module_utils/simple_requests.py @@ -0,0 +1,32 @@ +from .api import TenableAPI +from .exceptions import TenableAPIError + + +def run_module( + module, endpoint: str, param: str = None, method: str = None, data: dict = None, query_params_func=None, **kwargs +): + tenable_api = TenableAPI(module) + changed = False + if param: + endpoint = f"{endpoint}/{param}" + query_params = {} + if query_params_func: + query_params = query_params_func(**kwargs) + try: + if method in ["POST", "PUT", "DELETE", "PATCH"]: + changed = True + response = tenable_api.request(method, endpoint, params=query_params, data=data) + else: + response = tenable_api.request(method, endpoint, params=query_params) + module.exit_json(changed=changed, api_response=response) + except TenableAPIError as e: + module.fail_json(msg=str(e), status_code=getattr(e, "status_code", "Unknown")) + + +def run_module_with_file(module, endpoint: str, file_path: str): + tenable_api = TenableAPI(module) + try: + response = tenable_api.upload_file(endpoint, file_path) + module.exit_json(changed=True, api_response=response) + except TenableAPIError as e: + module.fail_json(msg=str(e), status_code=getattr(e, "status_code", "Unknown")) diff --git a/plugins/modules/add_agent_to_group.py b/plugins/modules/add_agent_to_group.py new file mode 100644 index 0000000..c7ed1a2 --- /dev/null +++ b/plugins/modules/add_agent_to_group.py @@ -0,0 +1,71 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: add_agent_to_group +short_description: Adds an agent to the agent group. +version_added: "0.0.1" +description: + - This module adds an agent to the agent group. + - You can use the get_agent_group_details module to verify that the agent was added to the specified agent group. + - This module is made from https://developer.tenable.com/reference/agent-groups-add-agent docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + group_id: + description: + - The ID of the agent group. + required: true + type: str + agent_id: + description: + - The ID of the agent to add. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Add agent to a existing agent group + add_agent_to_group: + access_key: "your_access_key" + secret_key: "your_secret_key" + group_id: "123456" + agent_id: "654321" + +- name: Add an agent to a group using enviroment creds + add_agent_to_group: + group_id: "{{ tenable_group_id }}" + agent_id: "{{ tenable_agent_id }}" +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "agent_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agent-groups/{module.params['group_id']}/agents/{module.params['agent_id']}" + + run_module(module, endpoint, method="PUT") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/add_agents_to_a_group.py b/plugins/modules/add_agents_to_a_group.py new file mode 100644 index 0000000..ce945f8 --- /dev/null +++ b/plugins/modules/add_agents_to_a_group.py @@ -0,0 +1,103 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: add_agents_to_a_group +short_description: Creates a bulk operation task to add agents to a group. +version_added: "0.0.1" +description: + - This module creates a bulk operation task to add agents to a group. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module was made with https://developer.tenable.com/reference/bulk-add-agents docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group + - valkiriaaquatica.tenable.bulk +""" + +EXAMPLES = r""" +- name: Add agents to a group filtering + add_agents_to_a_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "123456789" + criteria: + filters: ["name:match:laptop"] + all_agents: true + wildcard: "wildcard" + filter_type: "and" + hardcoded_filters: ["name:match:office"] + items: ["12345", "65789"] + not_items: ["98765"] + +- name: Add agents to a group using enviroment creds and no filters + add_agents_to_a_group: + group_id: "123456789" + items: ["12345", "65789"] +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: ID of the created task. + type: str + container_uuid: + description: UUID of the container. + type: str + status: + description: Status of the task. + type: str + message: + description: Message about the task status. + type: str + start_time: + description: Start time of the task. + type: int + last_update_time: + description: Last update time of the task. + type: int + total_work_units: + description: Total work units of the task. + type: int + total_work_units_completed: + description: Total work units completed. + type: int + completion_percentage: + description: Completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "criteria", "group_id", "items", "not_items") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agent-groups/{module.params['group_id']}/agents/_bulk/add" + + payload_keys = ["criteria", "items", "not_items"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/add_agents_to_a_network.py b/plugins/modules/add_agents_to_a_network.py new file mode 100644 index 0000000..2770c0c --- /dev/null +++ b/plugins/modules/add_agents_to_a_network.py @@ -0,0 +1,111 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: add_agents_to_a_network +short_description: Creates a bulk operation task to add agents to a custom network. +version_added: "0.0.1" +description: + - This module creates a bulk operation task to add agents to a custom network. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - Is set to network_uuid differente from other modules where is network_id because here creates the payload. + - This module was made with https://developer.tenable.com/reference/io-agent-bulk-operations-add-to-network docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +options: + network_uuid: + description: + - The UUID of the network object you want to update. + - You cannot update the default network object. + - To list all networks and get the ID use the list_networks moduke. + required: true + type: str +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.bulk +""" + +EXAMPLES = r""" +- name: Add agents to a group filtering + add_agents_to_a_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_uuid: "123456789" + criteria: + filters: ["name:match:laptop"] + all_agents: true + wildcard: "wildcard" + filter_type: "and" + hardcoded_filters: ["name:match:office"] + items: ["12345", "65789"] + not_items: ["98765"] + +- name: Add agents to a group using enviroment creds and no filters + add_agents_to_a_network: + network_uuid: "123456789" + items: ["12345", "65789"] +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: The unique identifier for the task. + type: str + container_uuid: + description: The UUID of the container. + type: str + status: + description: The current status of the task. + type: str + message: + description: A message describing the current state of the task. + type: str + start_time: + description: The start time of the task in epoch milliseconds. + type: int + last_update_time: + description: The last update time of the task in epoch milliseconds. + type: int + total_work_units: + description: The total number of work units for the task. + type: int + total_work_units_completed: + description: The number of work units completed for the task. + type: int + completion_percentage: + description: The completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "criteria", "items", "not_items", "network_uuid") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scanners/null/agents/_bulk/addToNetwork" + + payload_keys = ["network_uuid", "criteria", "items", "not_items"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/add_or_remove_asset_tags.py b/plugins/modules/add_or_remove_asset_tags.py new file mode 100644 index 0000000..0de0cf7 --- /dev/null +++ b/plugins/modules/add_or_remove_asset_tags.py @@ -0,0 +1,130 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: add_or_remove_asset_tags +short_description: Adds or removes tags to/from assets. +version_added: "0.0.1" +description: + - This module adds or removes tags to/from assets. + - Adds a tag value to asset. + - This module is made from https://developer.tenable.com/reference/tags-assign-asset-tags docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + action: + description: + - Specifies whether to add or remove tags. + type: str + required: true + choices: + - add + - remove + assets: + description: + - An array of asset UUIDs to which the tags should be added or from which tags should be removed. + - For more information on determining values for this array https://developer.tenable.com/docs/determine-tag-identifiers-tio + type: list + elements: str + required: true + tags: + description: + - A list of tag value UUIDs to be assigned or removed. + - For more information on determining values for this array https://developer.tenable.com/docs/determine-tag-identifiers-tio + type: list + elements: str + required: true +author: + - Fernando Mendieta Ovejero (@fernandomendietaovejero) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Add the tag to an asset + add_or_remove_asset_tags: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + action: add + assets: "123456789" + tags: "first_tag_uuid,second_tag_uuid" + +- name: Remove a tag using enviroment creds + add_or_remove_asset_tags: + action: remove + assets: "{{ asset }}" + tags: "{{ tag }}" + register: remove_tag_to_asset + +- name: Add the tag to several assets + add_or_remove_asset_tags: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + action: add + assets: "first_asset,second_asset" + tags: "new_tag" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + data: + description: Contains specific data about the operation. + type: dict + returned: always + contains: + job_uuid: + description: Unique identifier for the job created. + type: str + returned: always + sample: "123456789" + status_code: + description: HTTP status code returned by the Tenable.io API. + type: int + returned: always + sample: 200 + changed: + description: Indicates if any change was made by the operation. + type: bool + returned: always + sample: true +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + # no other module has this arguments + specific_spec = { + "action": {"required": True, "type": "str", "choices": ["add", "remove"]}, + "assets": {"required": True, "type": "list", "elements": "str"}, + "tags": {"required": True, "type": "list", "elements": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + ) + payload_keys = ["action", "assets", "tags"] + payload = build_payload(module, payload_keys) + endpoint = "tags/assets/assignments" + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/add_scanner_to_scanner_group.py b/plugins/modules/add_scanner_to_scanner_group.py new file mode 100644 index 0000000..862575d --- /dev/null +++ b/plugins/modules/add_scanner_to_scanner_group.py @@ -0,0 +1,60 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: add_scanner_to_scanner_group +short_description: Adds a scanner to the scanner group. +version_added: "0.0.1" +description: + - This module adds a scanner to the specified scanner group. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/scanner-groups-add-scanner docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Add a scanner to a scanner group + add_scanner_to_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 12345 + scanner_id: 67890 + +- name: Add a scanner to a scanner group using enviroment creds + add_scanner_to_scanner_group: + group_id: 12345 + scanner_id: 67890 +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "scanner_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanner-groups/{module.params['group_id']}/scanners/{module.params['scanner_id']}" + + run_module(module, endpoint, method="POST") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/add_user_to_group.py b/plugins/modules/add_user_to_group.py new file mode 100644 index 0000000..4db1f26 --- /dev/null +++ b/plugins/modules/add_user_to_group.py @@ -0,0 +1,59 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: add_user_to_group +short_description: Add a user to a group. +version_added: "0.0.1" +description: + - This module adds a user to a specified group. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/groups-add-user docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.user + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Add a user to a group using explicit credentials + add_user_to_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 1234 + user_id: 5444 + +- name: Add a user to a group using environment credentials + add_user_to_group: + group_id: 1234 + user_id: 5444 +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "user_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"groups/{module.params['group_id']}/users/{module.params['user_id']}" + + run_module(module, endpoint, method="POST") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/agent_profile_operations.py b/plugins/modules/agent_profile_operations.py new file mode 100644 index 0000000..390c0a3 --- /dev/null +++ b/plugins/modules/agent_profile_operations.py @@ -0,0 +1,135 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: agent_profile_operations +short_description: Assign or remove agents from a profile. +version_added: "0.0.1" +description: + - This modulecCreates a bulk operation task to either assign agents to a profile or to remove agents from a profile. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/agent-bulk-operations-profile docs. +options: + action: + description: + - The action to perform assign or remove agents from a profile. + - It is nto specified in the Tenable docs but is added because it helps. + required: true + type: str + choices: ["assign", "remove"] + profile_uuid: + description: + - The UUID of the profile to assign the agents to (only for assign action). + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.bulk +""" + +EXAMPLES = r""" +- name: Assign agents to a profile + agent_profile_operations: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + action: "assign" + profile_uuid: "1233" + criteria: + all_agents: true + wildcard: "wildcard" + filters: ["name:match:laptop"] + filter_type: "and" + hardcoded_filters: ["name:match:office"] + items: + - "34334" + not_items: + - "98765" + +- name: Remove agents from a profile using enviroment creds + agent_profile_operations: + action: "remove" + items: + - "22333" + - "1111" + not_items: + - "3434" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: The unique identifier for the task. + type: str + container_uuid: + description: The UUID of the container. + type: str + status: + description: The current status of the task. + type: str + message: + description: A message describing the current state of the task. + type: str + start_time: + description: The start time of the task in epoch milliseconds. + type: int + last_update_time: + description: The last update time of the task in epoch milliseconds. + type: int + total_work_units: + description: The total number of work units for the task. + type: int + total_work_units_completed: + description: The number of work units completed for the task. + type: int + completion_percentage: + description: The completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "criteria", "items", "not_items") + specific_spec = { + "action": {"required": True, "type": "str", "choices": ["assign", "remove"]}, + "profile_uuid": {"required": False, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + action = module.params["action"] + if action == "assign" and not module.params.get("profile_uuid"): + module.fail_json(msg="The 'profile_uuid' parameter is required when action is 'assign'.") + + endpoint = "scanners/null/agents/_bulk/assignToProfile" + payload_keys = ["criteria", "items", "not_items", "profile_uuid"] + payload = build_payload(module, payload_keys) + + if action == "remove": + payload.pop("profile_uuid", None) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/allow_control_of_running_scans.py b/plugins/modules/allow_control_of_running_scans.py new file mode 100644 index 0000000..8ffc0c5 --- /dev/null +++ b/plugins/modules/allow_control_of_running_scans.py @@ -0,0 +1,80 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +# in docs is the id of the scan is set to scan_uuid (idk why) it changes from other agent docs where is scan_id +# to maintain the consistency the scan_uuid of docs is set to scan_id + +DOCUMENTATION = r""" +--- +module: allow_control_of_running_scans +short_description: Allows control of scans that are currently running on a scanner. +version_added: "0.0.1" +description: + - This module allows control of scans that are currently running on a scanner. + - Note You cannot use this endpoint to update the credential type. + - Requires SCAN MANAGER [40] credential permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/scanners-control-scans docs. +options: + action: + description: + - An action to perform on a scan. Valid values are stop, pause, and resume. + type: str + required: true + choices: ["stop", "pause", "resume"] +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Resumes scans in a specified scanner + allow_control_of_running_scans: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scanner_id: 123456 + scan_id: 987 + action: "resume" + +- name: Stops scans in a specified scanner using environment creds + allow_control_of_running_scans: + scanner_id: 123456 + scan_id: 987 + action: "stop" +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scanner_id", "scan_id") + specific_spec = {"action": {"required": True, "type": "str", "choices": ["stop", "pause", "resume"]}} + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/{module.params['scanner_id']}/scans/{module.params['scan_id']}" + + payload_keys = ["action"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/assign_attributes_to_asset.py b/plugins/modules/assign_attributes_to_asset.py new file mode 100644 index 0000000..afe0b0c --- /dev/null +++ b/plugins/modules/assign_attributes_to_asset.py @@ -0,0 +1,96 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: assign_attributes_to_asset +short_description: Assigns custom asset attributes to the specified asset. +version_added: "0.0.1" +description: + - This module assigns custom asset attributes to the specified asset. + - In Tenable.IO is specifed asset_id but the module changes to asset_uuid to keep the order in the collection. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - The module is made from https://developer.tenable.com/reference/io-v3-asset-attributes-assign docs. +options: + attributes: + description: + - An array of custom asset attribute values to assign to the specified asset. + type: list + elements: dict + required: true + suboptions: + id: + description: + - The ID of the custom asset attribute you want to assign to the asset. + type: str + required: true + value: + description: + - The value of the custom asset attribute. For example, for a custom asset attribute named Location you could assign a value of Dallas. + type: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset +""" + +EXAMPLES = r""" +- name: Assign custom asset attributes to an asset + assign_attributes_to_asset: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "12345" + attributes: + - id: "123456" + value: "Dallas" + +- name: Assign custom asset attributes to an asset using enviroment creds + assign_attributes_to_asset: + asset_id: "12345" + attributes: + - id: "123456" + value: "Dallas" + - id: "23897" + value: "Paris" +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "asset_uuid") + specific_spec = { + "attributes": { + "required": True, + "type": "list", + "elements": "dict", + "options": {"id": {"required": True, "type": "str"}, "value": {"required": True, "type": "str"}}, + } + } + + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"api/v3/assets/{module.params['asset_uuid']}/attributes" + payload_keys = ["attributes"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/assign_scanners.py b/plugins/modules/assign_scanners.py new file mode 100644 index 0000000..ea13f75 --- /dev/null +++ b/plugins/modules/assign_scanners.py @@ -0,0 +1,74 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: assign_scanners +short_description: Associates a scanner or scanner group with a network object. +version_added: "0.0.1" +description: + - This module associates a scanner or scanner group with a network object. + - Use this endpoint to + Assign a scanner or scanner group to a custom network object. + Return a scanner or scanner group to the default network object. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/networks-assign-scanner docs. +options: + scanner_uuid: + description: + - The UUID of the scanner or scanner group you want to assign to the network object. + type: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.network +""" + +EXAMPLES = r""" +- name: Assign a scanner to a network object + assign_scanners: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "12345" + scanner_uuid: "9874" + +- name: Assign a scanner group to a network object using environment credentials + assign_scanners: + network_id: "12345" + scanner_uuid: "9874" +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_repeated_special_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = {"scanner_uuid": {"required": True, "type": "str"}} + special_spec = get_repeated_special_spec("network_id") + + argument_spec = {**common_spec, **specific_spec, **special_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"networks/{module.params['network_id']}/scanners/{module.params['scanner_uuid']}" + + run_module(module, endpoint, method="POST") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/assign_single_attribute_to_asset.py b/plugins/modules/assign_single_attribute_to_asset.py new file mode 100644 index 0000000..2155523 --- /dev/null +++ b/plugins/modules/assign_single_attribute_to_asset.py @@ -0,0 +1,75 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: assign_single_attribute_to_asset +short_description: Assigns a single custom asset attribute to the specified asset. +version_added: "0.0.1" +description: + - This module assigns a single custom asset attribute to the specified asset. + - Module made from https://developer.tenable.com/reference/io-v3-asset-attributes-single-update docs. + - Requires BASIC [16] user permissions +options: + value: + description: + - The value of the custom asset attribute. + - For example, for a custom asset attribute named Location you could assign a value of Dallas. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset + - valkiriaaquatica.tenable.attributes +""" + +EXAMPLES = r""" +- name: Update tag value + assign_single_attribute_to_asset: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "123456" + attribute_id: "987465" + value: "this_is_the_new_value" + +- name: Update tag value with envirometn creds and new filters + assign_single_attribute_to_asset: + asset_uuid: "123456" + attribute_id: "9874" + value: "this_is_the_new_value" +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid", "attribute_id", "value") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + payload_keys = ["value"] + payload = build_payload(module, payload_keys) + + endpoint = f"api/v3/assets/{module.params['asset_uuid']}/attributes/{module.params['attribute_id']}" + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/check_agent_group_operation_status.py b/plugins/modules/check_agent_group_operation_status.py new file mode 100644 index 0000000..21681c4 --- /dev/null +++ b/plugins/modules/check_agent_group_operation_status.py @@ -0,0 +1,101 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: check_agent_group_operation_status +short_description: Check the status of a bulk operation on an agent group. +version_added: "0.0.1" +description: + - This module checks the status of a bulk operation on an agent group. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/bulk-task-agent-group-status docs. +options: + task_uuid: + description: + - The UUID of the task. + type: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Check the status of a bulk operation on an agent group + check_agent_group_operation_status: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "1233" + task_uuid: "321" + +- name: Check the status of a bulk operation on an agent group using environment creds + check_agent_group_operation_status: + group_id: "1233" + task_uuid: "321" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: The unique identifier for the task. + type: str + container_uuid: + description: The UUID of the container. + type: str + status: + description: The current status of the task. + type: str + message: + description: A message describing the current state of the task. + type: str + start_time: + description: The start time of the task in epoch milliseconds. + type: int + last_update_time: + description: The last update time of the task in epoch milliseconds. + type: int + end_time: + description: The end time of the task in epoch milliseconds. + type: int + total_work_units: + description: The total number of work units for the task. + type: int + total_work_units_completed: + description: The number of work units completed for the task. + type: int + completion_percentage: + description: The completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "group_id") + specific_spec = {"task_uuid": {"required": True, "type": "str"}} + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agent-groups/{module.params['group_id']}/agents/_bulk/{module.params['task_uuid']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/check_agent_operation_status.py b/plugins/modules/check_agent_operation_status.py new file mode 100644 index 0000000..de80559 --- /dev/null +++ b/plugins/modules/check_agent_operation_status.py @@ -0,0 +1,98 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: check_agent_operation_status +short_description: Check the status of a bulk operation on agents. +version_added: "0.0.1" +description: + - This module checks the status of a bulk operation on agents. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/bulk-task-agent-status docs. +options: + task_uuid: + description: + - The UUID of the task. + type: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Check the status of a bulk operation on agents + check_agent_operation_status: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + task_uuid: "321" + +- name: Check the status of a bulk operation on agents using environment creds + check_agent_operation_status: + task_uuid: "321" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: The unique identifier for the task. + type: str + container_uuid: + description: The UUID of the container. + type: str + status: + description: The current status of the task. + type: str + message: + description: A message describing the current state of the task. + type: str + start_time: + description: The start time of the task in epoch milliseconds. + type: int + last_update_time: + description: The last update time of the task in epoch milliseconds. + type: int + end_time: + description: The end time of the task in epoch milliseconds. + type: int + total_work_units: + description: The total number of work units for the task. + type: int + total_work_units_completed: + description: The number of work units completed for the task. + type: int + completion_percentage: + description: The completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = {"task_uuid": {"required": True, "type": "str"}} + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agents/_bulk/{module.params['task_uuid']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/check_scan_export_status.py b/plugins/modules/check_scan_export_status.py new file mode 100644 index 0000000..76361a6 --- /dev/null +++ b/plugins/modules/check_scan_export_status.py @@ -0,0 +1,87 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: check_scan_export_status +short_description: Check the file status of an exported scan. +version_added: "0.0.1" +description: + - This module check the file status of an exported scan. + - When an export has been requested, it is necessary to poll this endpoint until a "ready" status + is returned, at which point the file is complete and can be downloaded using the export download endpoint + - Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions user permissions as specified in the Tenable.io API documentation. + - The module is made from https://developer.tenable.com/reference/scans-export-status docs. +options: + file_id: + description: + - The ID of the file to poll (included in response from /scans/{scan_id}/export). + type: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Check scan export statu using enviroment creds + check_scan_export_status: + scan_id: "1234" + file_id: "24332r34-csv" + +- name: Check scan export status using enviroment creds + check_scan_export_status: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "1234" + file_id: "24332r34-csv" +""" + +RETURN = r""" +api_response: + description: The full API response from Tenable. + returned: success + type: dict + contains: + data: + description: The actual data returned from the API. + type: dict + contains: + status: + description: The status of the export job. + type: str + status_code: + description: The HTTP status code returned by the API. + type: int + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id") + specific_spec = {"file_id": {"required": True, "type": "str"}} + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scans/{module.params['scan_id']}/export/{module.params['file_id']}/status" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/copy_scan.py b/plugins/modules/copy_scan.py new file mode 100644 index 0000000..e94f076 --- /dev/null +++ b/plugins/modules/copy_scan.py @@ -0,0 +1,142 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: copy_scan +short_description: Copies the specified scan. +version_added: "0.0.1" +description: + - This module copies the specified scan. + - Module made from https://developer.tenable.com/reference/scans-copy docs. + - Requires SCAN OPERATOR [24] user permissions and CAN EDIT [64] scan permissions +options: + name: + description: + - The name of the object. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan + - valkiriaaquatica.tenable.folder +""" + +EXAMPLES = r""" +- name: Copy scan using enviroment creds + copy_scan: + scan_id: "12345" + folder_id: "67890" + name: "new scan" + +- name: Copy scan + copy_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "12345" + folder_id: "67890" + name: "new scan" +""" + +RETURN = r""" +api_response: + description: The full API response from Tenable. + returned: success + type: dict + contains: + data: + description: The actual data returned from the API. + type: dict + contains: + control: + description: Indicates control status. + type: bool + sample: true + creation_date: + description: Creation date of the object. + type: str + sample: null + enabled: + description: Indicates if the object is enabled. + type: int + sample: 1 + id: + description: ID of the object. + type: int + sample: 2282 + last_modification_date: + description: Last modification date of the object. + type: str + sample: null + name: + description: Name of the object. + type: str + sample: "copy_sca_update_anisble" + owner: + description: Owner of the object. + type: str + sample: null + read: + description: Indicates if the object is read. + type: bool + sample: false + rrules: + description: Recurrence rules. + type: str + sample: "FREQ=WEEKLY;INTERVAL=4;BYDAY=SU" + shared: + description: Indicates if the object is shared. + type: bool + sample: false + starttime: + description: Start time of the object. + type: str + sample: "20240405T100000" + status: + description: Status of the object. + type: str + sample: "empty" + timezone: + description: Timezone of the object. + type: str + sample: "Europe/Madrid" + user_permissions: + description: User permissions for the object. + type: str + sample: null + uuid: + description: UUID of the object. + type: str + sample: "1111122269a" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_repeated_special_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id", "folder_id") + special_spec = get_repeated_special_spec("name") + argument_spec = {**common_spec, **special_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/copy" + payload_keys = ["folder_id", "name"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_agent_exclusion.py b/plugins/modules/create_agent_exclusion.py new file mode 100644 index 0000000..57cdbb4 --- /dev/null +++ b/plugins/modules/create_agent_exclusion.py @@ -0,0 +1,141 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_agent_exclusion +short_description: Creates a new agent exclusion. +version_added: "0.0.1" +description: + - This module creates a new agent exclusion. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/agent-exclusions-create docs. +options: + description: + description: + - The description of the exclusion. + type: str + required: false +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.name + - valkiriaaquatica.tenable.schedule +""" + +EXAMPLES = r""" +- name: Create agent exclusion + create_agent_exclusion: + access_key: "your_access_key" + secret_key: "your_secret_key" + name: "name" + description: "description" + schedule: + enabled: true + starttime: "2023-01-01 00:00:00" + endtime: "2023-12-31 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + tags: create_agent_exclusion +""" + +RETURN = r""" +api_response: + description: Contains the raw response from the Tenable API. + returned: success + type: dict + contains: + id: + description: The ID of the created exclusion. + type: int + sample: 124234 + name: + description: The name of the created exclusion. + type: str + sample: "Routers" + description: + description: The description of the created exclusion. + type: str + sample: "Router scan exclusion" + creation_date: + description: The date when the exclusion was created. + type: int + sample: 1543541807 + last_modification_date: + description: The date when the exclusion was last modified. + type: int + sample: 1543541807 + schedule: + description: Schedule details for the exclusion. + type: dict + contains: + endtime: + description: The end time of the schedule. + type: str + sample: "2019-12-31 19:35:00" + enabled: + description: Indicates if the schedule is enabled. + type: bool + sample: true + rrules: + description: Recurrence rules for the schedule. + type: dict + contains: + freq: + description: Frequency of the recurrence. + type: str + sample: "DAILY" + interval: + description: Interval for the recurrence. + type: int + sample: 8 + byweekday: + description: Days of the week for the recurrence. + type: str + sample: "SU,MO" + bymonthday: + description: Days of the month for the recurrence. + type: int + sample: 9 + timezone: + description: Timezone for the schedule. + type: str + sample: "US/Pacific" + starttime: + description: The start time of the schedule. + type: str + sample: "2018-12-31 19:35:00" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "schedule", "name", "description") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scanners/null/agents/exclusions" + + payload_keys = ["name", "description", "schedule"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_agent_group.py b/plugins/modules/create_agent_group.py new file mode 100644 index 0000000..88cac30 --- /dev/null +++ b/plugins/modules/create_agent_group.py @@ -0,0 +1,145 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_agent_group +short_description: Creates an agent group. +version_added: "0.0.1" +description: + - This module creates an agent group. + - The module is made from https://developer.tenable.com/reference/agent-groups-create docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + name: + description: + - The name of the agent group. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Create agent group using enviroment creds + create_agent_group: + name: "name" + +- name: Create agent group passing creds as vars + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "name" +""" + + +RETURN = r""" +api_response: + description: Response returned by the Tenable API after a request. + type: dict + returned: always + contains: + data: + description: Contains detailed information about the agent group. + type: dict + returned: always + contains: + agents_count: + description: Number of agents in the group. + type: int + returned: always + sample: 0 + creation_date: + description: The date when the agent group was created. + type: str + returned: always + sample: "2021-01-01T00:00:00Z" + id: + description: Unique identifier for the agent group. + type: int + returned: always + sample: 123456 + last_modification_date: + description: The date when the agent group was last modified. + type: str + returned: always + sample: "2021-01-02T00:00:00Z" + name: + description: The name of the agent group. + type: str + returned: always + sample: "example_group" + owner: + description: The id that owns the agent group. + type: str + returned: always + sample: "system" + owner_id: + description: The ID of the owner. + type: int + returned: always + sample: 123456 + owner_name: + description: The name of the owner. + type: str + returned: always + sample: "system" + owner_uuid: + description: The UUID of the owner. + type: str + returned: always + sample: "123456" + shared: + description: Indicates if the group is shared with other users. + type: int + returned: always + sample: 0 + timestamp: + description: The timestamp when the last modification was made. + type: int + returned: always + sample: 1610000000 + user_permissions: + description: The permissions associated with the user. + type: int + returned: always + sample: 0 + uuid: + description: The UUID of the agent group. + type: str + returned: always + sample: "123456" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = {"name": {"required": True, "type": "str"}} + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scanners/null/agent-groups" + + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_attribute.py b/plugins/modules/create_attribute.py new file mode 100644 index 0000000..c1cf6cc --- /dev/null +++ b/plugins/modules/create_attribute.py @@ -0,0 +1,91 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_attribute +short_description: Creates a new custom asset attribute. +version_added: "0.0.1" +description: + - This module creates a new custom asset attribute in Tenable.io. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - The module is made from https://developer.tenable.com/reference/io-v3-asset-attributes-create docs. +options: + attributes: + description: + - An array of new custom asset attributes. + type: list + elements: dict + required: true + suboptions: + name: + description: + - The name of the custom asset attribute that you want to create. For example, Location. + type: str + required: false + description: + description: + - A description of the custom asset attribute. + type: str + required: false +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Create two attributes + create_attribute: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + attributes: + - name: "Location" + description: "The geographical location of the asset" + - name: "Department" + description: "The department to which the asset belongs" + +- name: Create two attributes using enviroment creds + create_attribute: + attributes: + - name: "Office" +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "attributes": { + "required": True, + "type": "list", + "elements": "dict", + "options": {"name": {"required": False, "type": "str"}, "description": {"required": False, "type": "str"}}, + } + } + + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "api/v3/assets/attributes" + payload_keys = ["attributes"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_exclusion.py b/plugins/modules/create_exclusion.py new file mode 100644 index 0000000..d359c2f --- /dev/null +++ b/plugins/modules/create_exclusion.py @@ -0,0 +1,183 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: create_exclusion +short_description: Creates a new exclusion. +version_added: "0.0.1" +description: + - This module manages agents in Tenable, allowing to add or remove agents from specific groups. + - This module is made from https://developer.tenable.com/reference/exclusions-create docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + name: + description: + - The name of the exclusion. + type: str + required: true + members: + description: + - The targets that you want excluded from scans. Specify multiple targets as a comma-separated string. + - An individual IPv4 address (192.0.2.1) + - A range of IPv4 addresses (192.0.2.1-192.0.2.255) + - CIDR notation (192.0.2.0/24) + - A fully-qualified domain name (FQDN) (host.domain.com) + type: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.exclusion_params + - valkiriaaquatica.tenable.schedule +""" + +EXAMPLES = r""" +- name: Create a exclusion using enviroment creds + create_exclusion: + name: "name" + description: "i am the exclusion" + members: "192.168.1.10" + schedule: + enabled: true + starttime: "2023-04-01 09:00:00" + endtime: "2023-04-01 17:00:00" + timezone: "America/New_York" + rrules: + freq: "WEEKLY" + interval: 1 + byweekday: "MO,TU,WE,TH,FR" + network_id: "123456" + +- name: Create a exclusion using variable creds + create_exclusion: + access_key: "your_access_key" + secret_key: "your_secret_key" + name: "name" + members: "192.168.1.10,192.168.1.11" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains the data of the exclusion that was created or updated. + type: dict + contains: + creation_date: + description: UNIX timestamp when the exclusion was created. + type: int + returned: always + sample: 123456 + description: + description: Description of the exclusion. + type: str + returned: always + sample: "i am the exclusion" + id: + description: Unique identifier of the exclusion. + type: int + returned: always + sample: 123456 + last_modification_date: + description: UNIX timestamp when the exclusion was last modified. + type: int + returned: always + sample: 123456 + members: + description: IP addresses or other identifiers included in the exclusion. + type: str + returned: always + sample: "192.168.1.120" + name: + description: Name of the exclusion. + type: str + returned: always + sample: "exclusion" + network_id: + description: Network ID associated with the exclusion. + type: str + returned: always + sample: "123456" + schedule: + description: Schedule details for when the exclusion is active. + type: dict + returned: always + contains: + enabled: + description: Whether the schedule is enabled. + type: bool + sample: true + endtime: + description: End time of the exclusion schedule. + type: str + sample: "2023-04-01 17:00:00" + rrules: + description: Recurrence rules for the exclusion schedule. + type: dict + contains: + bymonthday: + description: Day of the month the exclusion recurs on (if applicable). + type: str + returned: when applicable + sample: null + byweekday: + description: Days of the week the exclusion recurs on. + type: str + sample: "MO,TU,WE,TH,FR" + freq: + description: Frequency of the recurrence. + type: str + sample: "WEEKLY" + interval: + description: Interval at which the recurrence repeats. + type: int + sample: 1 + starttime: + description: Start time of the exclusion schedule. + type: str + sample: "2023-04-01 09:00:00" + timezone: + description: Timezone of the exclusion schedule. + type: str + sample: "America/New_York" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "description", "schedule", "network_id") + specific_spec = { + "name": {"required": True, "type": "str"}, + "members": {"required": True, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + payload_keys = ["name", "members", "description", "schedule", "network_id"] + payload = build_payload(module, payload_keys) + + endpoint = "exclusions" + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_folder.py b/plugins/modules/create_folder.py new file mode 100644 index 0000000..5412b4a --- /dev/null +++ b/plugins/modules/create_folder.py @@ -0,0 +1,66 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_folder +short_description: Creates a new custom folder for the current user. +version_added: "0.0.1" +description: + - This module creates a new custom folder for the current user. + - There is a rate limit of 10 folder creation requests per minute. + - The module is made from https://developer.tenable.com/reference/folders-create docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Create folder using enviroment creds + create_folder: + name: "name" + +- name: Create folder passing creds as vars + create_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "name" +""" + + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "name") + specific_spec = {"name": {"required": True, "type": "str"}} + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "folders" + + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_group.py b/plugins/modules/create_group.py new file mode 100644 index 0000000..39a7c55 --- /dev/null +++ b/plugins/modules/create_group.py @@ -0,0 +1,76 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_group +short_description: Create a group. +version_added: "0.0.1" +description: + - This module create a group. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/groups-create docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Create a new group + create_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + uuid: + description: The unique identifier for the group. + type: str + name: + description: The name of the group. + type: str + permissions: + description: The permissions of the group. + type: int + container_uuid: + description: The UUID of the container. + type: str + id: + description: The ID of the group. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "name") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "groups" + + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_managed_credential.py b/plugins/modules/create_managed_credential.py new file mode 100644 index 0000000..98454dd --- /dev/null +++ b/plugins/modules/create_managed_credential.py @@ -0,0 +1,249 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: create_managed_credential +short_description: Creates a managed credential object that you can use when configuring and running scans. +version_added: "0.0.1" +description: + - This module creates a managed credential object that you can use when configuring and running scans. + - You can grant other users the permission to use the managed credential object in scans and to edit the managed credential configuration. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/credentials-create docs. +options: + name: + description: + - The name of the managed credential. + - This name must be unique within your Tenable Vulnerability Management instance. + type: str + required: true + description: + description: + - The description of the managed credential object. + type: str + required: false + type: + description: + - The type of credential object. + - For a list of supported credential types, use the GET /credentials/types endpoint or use the list_credential_types module. + type: str + required: true + settings: + description: + - The configuration settings for the credential. + - The parameters of this object vary depending on the credential type. + - For more information, see https://developer.tenable.com/docs/determine-settings-for-credential-type . + - Note This form displays limited parameters that support a Windows type of credential that uses password authentication. + required: true + type: dict + suboptions: + domain: + description: + - The Windows domain to which the username belongs. + required: false + type: str + username: + description: + - The username on the target system. + required: false + type: str + auth_method: + description: + - The name for the authentication method. + - This value corresponds to the credentials[].types[].configuration[].options[].id attribute in the + response message of list_credential_types module. + required: false + type: str + password: + description: + - The user password on the target system. + required: false + type: str + permissions: + description: + - A list of user permissions for the managed credential. + - If a request message omits this parameter, Tenable Vulnerability Management automatically creates + a permissions object for the user account that submits the request. + required: false + type: list + elements: dict + suboptions: + grantee_uuid: + description: + - The UUID of the user or user group granted permissions for the managed credential. + - This parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential. + required: false + type: str + type: + description: + - A value specifying whether the grantee is a user (user) or a user group (group). + - This parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential. + required: false + type: str + permissions: + description: + - A value specifying the permissions granted to the user or user group for the credential. + - 32—The user can view credential information and use the credential in scans. Corresponds + to the Can Use permission in the user interface. + - 64—The user can view and edit credential settings, delete the credential, and use the credential in scans. + Corresponds to the Can Edit permission in the user interface. + - This parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential. + required: false + type: int + name: + description: + - The name of the user or user group that you want to grant permissions for the managed credential. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Create managed credential + create_managed_credential: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "name" + description: "description" + type: "type" + settings: + domain: "domain" + username: "username" + auth_method: "Password" + password: "password" + permissions: + - grantee_uuid: "grantee_uuid" + type: "user" + permissions: 64 + name: "name" + tags: create_managed_credential + +- name: Create managed credential using enviroment creds + create_managed_credential: + name: "name" + description: "description" + type: "type" + settings: + domain: "domain" + username: "username" + auth_method: "Password" + password: "password" +""" + +RETURN = r""" +api_response: + description: Contains the raw response from the Tenable API. + returned: success + type: dict + contains: + id: + description: The ID of the created credential. + type: str + sample: "1111122269a" + name: + description: The name of the created credential. + type: str + sample: "Managed Credential Name" + description: + description: The description of the created credential. + type: str + sample: "Credential Description" + type: + description: The type of the created credential. + type: str + sample: "Windows" + settings: + description: Configuration settings for the credential. + type: dict + contains: + domain: + description: The domain for the credential. + type: str + sample: "domain" + username: + description: The username for the credential. + type: str + sample: "username" + auth_method: + description: The authentication method for the credential. + type: str + sample: "Password" + permissions: + description: List of user permissions for the managed credential. + type: list + elements: dict + contains: + grantee_uuid: + description: The UUID of the user or user group granted permissions. + type: str + sample: "grantee_uuid" + type: + description: Specifies whether the grantee is a user or a user group. + type: str + sample: "user" + permissions: + description: Permissions granted to the user or user group. + type: int + sample: 64 + name: + description: The name of the user or user group. + type: str + sample: "name" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "name", "description") + specific_spec = { # unique arguments + "type": {"required": True, "type": "str"}, + "settings": { + "required": True, + "type": "dict", + "options": { + "domain": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "auth_method": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "no_log": True}, + }, + }, + "permissions": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "grantee_uuid": {"required": False, "type": "str"}, + "type": {"required": False, "type": "str"}, + "permissions": {"required": False, "type": "int"}, + "name": {"required": False, "type": "str"}, + }, + }, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "credentials" + payload_keys = ["name", "description", "type", "settings", "permissions"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_network.py b/plugins/modules/create_network.py new file mode 100644 index 0000000..6d4d4a1 --- /dev/null +++ b/plugins/modules/create_network.py @@ -0,0 +1,138 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_network +short_description: Created a network. +version_added: "0.0.1" +description: + - This module creates a network object that you associate with scanners and scanner groups. + - The module is made from https://developer.tenable.com/reference/networks-create docs. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.network_params +""" + +EXAMPLES = r""" +- name: Create a network using enviroment creds + create_network: + name: "name" + description: "this is the descritpion" + assets_ttl_days: 50 + +- name: Create a network using enviroment creds + create_network: + access_key: "your_access_key" + secret_key: "your_secret_key" + name: "name" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains all relevant details about the network or asset. + type: dict + contains: + assets_ttl_days: + description: The time-to-live (TTL) in days for assets within this network. + type: int + returned: always + sample: 1 + created: + description: UNIX timestamp when the network was created. + type: int + returned: always + sample: 123456 + created_by: + description: User identifier of who created the network. + type: str + returned: always + sample: "123456" + created_in_seconds: + description: Creation time in seconds. + type: int + returned: always + sample: 123456 + description: + description: Description of the network. + type: str + returned: always + sample: "this is the description" + is_default: + description: Indicates whether this network is the default network. + type: bool + returned: always + sample: false + modified: + description: UNIX timestamp when the network was last modified. + type: int + returned: always + sample: 123456 + modified_by: + description: User identifier of who last modified the network. + type: str + returned: always + sample: "123456" + modified_in_seconds: + description: Modification time in seconds. + type: int + returned: always + sample: 123456 + name: + description: Name of the network. + type: str + returned: always + sample: "ansible_collection_test_network" + owner_uuid: + description: UUID of the owner of the network. + type: str + returned: always + sample: "123456" + scanner_count: + description: Number of scanners associated with this network. + type: int + returned: always + sample: 0 + uuid: + description: UUID of the network. + type: str + returned: always + sample: "123456" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "name", "description", "assets_ttl_days") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + payload_keys = ["name", "description", "assets_ttl_days"] + payload = build_payload(module, payload_keys) + + endpoint = "networks" + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_remediation_scan.py b/plugins/modules/create_remediation_scan.py new file mode 100644 index 0000000..beff26d --- /dev/null +++ b/plugins/modules/create_remediation_scan.py @@ -0,0 +1,382 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_remediation_scan +short_description: Create a remediation scan configuration. +version_added: "0.0.1" +description: + - This module creates a remediation scan configuration in Tenable.io. + - Note Tenable Vulnerability Management limits the number of scans you can create to 10,000 scans. + - Tenable recommends you re-use scheduled scans instead of creating new scans. + - An HTTP 403 error is returned if you attempt to create a scan after you have already reached the scan limit of 10,000. + - Requires SCAN OPERATOR [24] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/io-scans-remediation-create docs. +options: + uuid: + description: + - The UUID for the Tenable-provided remediation scan template to use. + - Use the list_templates module to list the available scan templates. + required: true + type: str + settings: + description: + - Settings object for the remediation scan. + required: true + type: dict + suboptions: + name: + description: + - The name of the scan. + required: true + type: str + description: + description: + - The description of the scan. + required: false + type: str + scanner_id: + description: + - The unique ID of the scanner to use. + - Use the list_scanners module to find de scanner id. + - You can use the special value AUTO-ROUTED to assign scan targets to scanner groups based on the groups' + configured scan route. + - For more information on Auto-Routed see https://developer.tenable.com/docs/manage-scan-routing-tio . + - If no scanner_id is passed in the request, Tenable assigns the US Cloud Scanner by default. + required: false + type: str + target_network_uuid: + description: + - Network UUID that targets the scan. + - This field is required if the scanner_id parameter is AUTO-ROUTED + - If your scans involve separate environments with overlapping IP ranges, specify the UUID of the network you want + to associate with the results of the auto-routed scan + - This value must match the network where you have assigned the scanner groups that you configured for scan routing. + - Note This parameter does not override network associations for scans that are not auto-routed. + - Tenable Vulnerability Management automatically associates a non-routed scan with the network to which you have + assigned the scanner that performs the scan. + required: false + type: str + scan_time_window: + description: + - Time window in minutes for when the scan can run. + - For Nessus Agent scans is the time frame in minutes during agents send data to tenable. If no value is passed, + tenable assigns 180 min. + - For Nessus Scanner is the time frame, in minutes, after which the scan will automatically stop. If no value + is passed, Tenable assigns 0 min. + required: false + type: int + text_targets: + description: + - The list of targets to scan. + - This parameter is required if your request omits other target parameters. + - Note that Tenable does not verify if values passed are correct or not. + required: false + type: str + target_groups: + description: + - DEPRECATED + required: false + type: list + elements: int + file_targets: + description: + - The name of a file containing the list of targets to scan. + - Before you use this parameter, use the upload_file module to upload the file to Tenable Vulnerability Management; then, + use the fileuploaded attribute of the response message as the file_targets parameter value. + - This parameter is required if your request omits other target parameters. + - Note Unicode/UTF-8 encoding is not supported in the targets file. + required: false + type: str + tag_targets: + description: + - The list of asset tag identifiers that the scan uses to determine which assets it evaluates. + - For more information about tag-based scans, see https://developer.tenable.com/docs/manage-tag-based-scans-tio . + - This parameter is required if your request omits other target parameters. + required: false + type: list + elements: str + agent_group_id: + description: + - An array of agent group UUIDs to scan. + - Required if the scan is an agent scan. + required: false + type: list + elements: str + emails: + description: + - Email addresses to notify when the scan completes. + - A comma-separated list of accounts that receive the email summary report. + required: false + type: str + acls: + description: + - A list of access control entries specifying permissions to apply to the scan. + - An array containing permissions to apply to the scan. + type: list + elements: dict + required: False + suboptions: + permissions: + description: + - The scan permission. + - For more information, refer to the Permissions section in the Tenable documentation. + type: int + required: False + owner: + description: + - Indicates whether the specified user or group owns the scan. + - Possible values are null (system-owned), 0 (not an owner), 1 (owner). + type: int + required: False + display_name: + description: + - The display name of the user or group in the Tenable Vulnerability Management UI. + type: str + required: False + name: + description: + - The name of the user or group granted the permissions. + type: str + required: False + id: + description: + - The identifier used to order the display of user or groups in the Permissions tab in the Tenable Vulnerability Management UI. + type: int + required: False + type: + description: + - The type of scan permission. + - "'default' for default permissions, 'user' for individual user permissions, 'group' for user group permissions." + type: str + required: False + enabled_plugins: + description: + - A comma-delimited list of plugins IDs to add to a remediation scan. + required: false + type: list + elements: int +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan_credentials +""" + +EXAMPLES = r""" +- name: Create a remediation scan with explicit credentials + create_remediation_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "12345" + settings: + name: "remediation scan" + description: "Scan to remediate issues" + scanner_id: "scanner_id" + target_network_uuid: "target_network_uuid" + scan_time_window: 180 + text_targets: "192.0.2.1/24" + file_targets: "targets.txt" + tag_targets: + - "tag1" + - "tag2" + agent_group_id: + - "agent_group_1" + - "agent_group_2" + emails: "user@example.com" + acls: + - permissions: 64 + owner: 1 + display_name: "Admin" + name: "admin" + id: 1 + type: "group" + credentials: + add: + Host: + Windows: + - domain: "domain" + username: "username" + auth_method: "Password" + password: "password" + enabled_plugins: + - 12345 + - 654212 + +- name: Create a remediation scan using environment credentials + create_remediation_scan: + uuid: "12345" + settings: + name: "remediation scan" + description: "Scan to remediate issues" + text_targets: "192.0.2.1/24" +""" + +RETURN = r""" +api_response: + description: Response from the Tenable.io API. + type: dict + returned: always + contains: + scan: + description: Details of the created scan. + type: dict + contains: + tag_type: + description: The tag type. + type: str + container_id: + description: The container ID. + type: str + owner_uuid: + description: The owner UUID. + type: str + uuid: + description: The UUID of the scan. + type: str + name: + description: The name of the scan. + type: str + description: + description: The description of the scan. + type: str + policy_id: + description: The policy ID. + type: int + scanner_id: + description: The scanner ID. + type: str + scanner_uuid: + description: The scanner UUID. + type: str + emails: + description: The emails associated with the scan. + type: str + sms: + description: The SMS associated with the scan. + type: str + enabled: + description: Indicates if the scan is enabled. + type: bool + include_aggregate: + description: Indicates if aggregate is included. + type: bool + scan_time_window: + description: The scan time window. + type: int + custom_targets: + description: The custom targets of the scan. + type: str + target_network_uuid: + description: The target network UUID. + type: str + auto_routed: + description: Indicates if the scan is auto-routed. + type: bool + remediation: + description: Indicates if the scan is a remediation scan. + type: bool + starttime: + description: The start time of the scan. + type: str + rrules: + description: The recurrence rules for the scan. + type: str + timezone: + description: The timezone of the scan. + type: str + notification_filters: + description: The notification filters of the scan. + type: str + shared: + description: Indicates if the scan is shared. + type: bool + user_permissions: + description: The user permissions for the scan. + type: int + default_permissions: + description: The default permissions for the scan. + type: int + owner: + description: The owner of the scan. + type: str + owner_id: + description: The owner ID. + type: int + last_modification_date: + description: The last modification date of the scan. + type: int + creation_date: + description: The creation date of the scan. + type: int + type: + description: The type of the scan. + type: str + id: + description: The ID of the scan. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "uuid", "credentials") + specific_spec = { + "uuid": {"required": True, "type": "str"}, + "settings": { # different from the one in arguments that uses create_scan module + "required": True, + "type": "dict", + "options": { + "name": {"required": True, "type": "str"}, + "description": {"required": False, "type": "str"}, + "scanner_id": {"required": False, "type": "str"}, + "target_network_uuid": {"required": False, "type": "str"}, + "scan_time_window": {"required": False, "type": "int"}, + "text_targets": {"required": False, "type": "str"}, + "target_groups": {"required": False, "type": "list", "elements": "int"}, + "file_targets": {"required": False, "type": "str"}, + "tag_targets": {"required": False, "type": "list", "elements": "str"}, + "agent_group_id": {"required": False, "type": "list", "elements": "str"}, + "emails": {"required": False, "type": "str"}, + "acls": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "permissions": {"required": False, "type": "int"}, + "owner": {"required": False, "type": "int"}, + "display_name": {"required": False, "type": "str"}, + "name": {"required": False, "type": "str"}, + "id": {"required": False, "type": "int"}, + "type": {"required": False, "type": "str"}, + }, + }, + }, + }, + "enabled_plugins": {"required": False, "type": "list", "elements": "int"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scans/remediation" + + payload_keys = ["uuid", "settings", "credentials", "enabled_plugins"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_report.py b/plugins/modules/create_report.py new file mode 100644 index 0000000..d537609 --- /dev/null +++ b/plugins/modules/create_report.py @@ -0,0 +1,130 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: create_report +short_description: Creates a report in PDF format based on the specified template and filters. +version_added: "0.0.1" +description: + - This module creates a report in PDF format based on the specified template and filters. + - Note Tenable Vulnerability Management limits the number of findings that can be included in a single report to 10,000. + - If you have more than 10,000 findings, Tenable recommends that you narrow the findings included in the report with a + filter or generate multiple reports. + - This module is made from https://developer.tenable.com/reference/vm-reports-create docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + name: + description: + - A name for the report. If omitted, a default name with a timestamp will be used. + required: false + type: str + template_name: + description: + - The type of template to use for the report. + required: true + type: str + choices: + - host_vulns_summary + - host_vulns_by_plugins + - host_vulns_by_assets + filters: + description: + - A set of filters to apply to the report. Filters can be used to narrow the vulnerabilities or assets included in the report. + required: false + type: list + elements: dict + suboptions: + property: + description: + - The property to filter the results by. + required: true + type: str + operator: + description: + - The comparison operator to apply to the filter. For example, eq, neq, gt, etc. + required: true + type: str + value: + description: + - The value to compare the given property to using the specified operator. + required: true + type: raw +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + + +EXAMPLES = r""" +- name: Create report with a plugin and enviroment vars + create_report: + name: "report_test" + template_name: "host_vulns_summary" + filters: + - property: "plugin_id" + operator: "eq" + value: [12345] + +- name: Create report using plugin.id and source filters + create_report: + name: "report_test" + template_name: "host_vulns_summary" + filters: + - property: "plugin_id" + operator: "eq" + value: [12345] + - property: "source" + operator: "eq" + value: ["AWS"] +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "name": {"required": False, "type": "str"}, + "template_name": { + "required": True, + "type": "str", + "choices": ["host_vulns_summary", "host_vulns_by_plugins", "host_vulns_by_assets"], + }, + "filters": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "property": {"required": True, "type": "str"}, + "operator": {"required": True, "type": "str"}, + "value": {"required": True, "type": "raw"}, + }, + }, + } + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "reports/export" + payload_keys = ["name", "template_name", "filters"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_scan.py b/plugins/modules/create_scan.py new file mode 100644 index 0000000..3a5e312 --- /dev/null +++ b/plugins/modules/create_scan.py @@ -0,0 +1,314 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_scan +short_description: Creates a scan configuration. +version_added: "0.0.1" +description: + - This module creates a scan configuration. + - For more information on the scan visit https://developer.tenable.com/docs/create-scan-tio. + - Note Tenable Vulnerability Management limits the number of scans you can create to 10,000 scans. + - Tenable recommends you re-use scheduled scans instead of creating new scans. + - An HTTP 403 error is returned if you attempt to create a scan after you have already reached the scan limit of 10,000. + - With SCAN OPERATOR [24] permissions, policy_id is required. + - This module is made from https://developer.tenable.com/reference/scans-create docs. + - Requires SCAN OPERATOR [24] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan_configuration + - valkiriaaquatica.tenable.scan_credentials +""" + + +EXAMPLES = r""" +- name: Create a scan with simple parameters, host_tagging and frequency scans + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + host_tagging: "yes" + refresh_reporting_frequency_scans: 2 + settings: + name: "{{ name_scan_creation }}" + agent_group_id: "{{ agent_group_id_created }}" + +- name: Create a scan with settings and using enviroment creds + create_scan: + uuid: "{{ template_scan_uuid }}" + settings: + name: "{{ name_scan_creation }}" + folder_id: 111 + scanner_id: 123456 + launch: "ON_DEMAND" + rrules: "WEEKLY" + timezone: "Atlantic/Madeira" + text_targets: "192.168.1.1,192.168.1.2" + +- name: Create a scan with a file of targets and passing credentials as variables and adding credentials + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan" + file_targets: scan_targets_file + credentials: + add: + Host: + Windows: + - domain: "domain" + username: "username" + auth_method: "Password" + password: "password" + + +- name: Create a scan configuring plugins and enviroment creds + create_scan: + uuid: "123456789" + settings: + name: "name" + policy_id: 12345 + plugin_configurations: + - plugin_family_name: "Red Hat Local Security Checks" + plugins: + - plugin_id: "79798" + status: "enabled" + - plugin_id: "79799" + status: "disabled" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response related to a scan configuration or outcome. + type: dict + returned: always + contains: + data: + description: Contains all relevant details about the scan. + type: dict + contains: + scan: + description: Details of the specific scan. + type: dict + contains: + agent_scan_launch_type: + description: Launch type of the scan, e.g., manual or scheduled. + type: str + returned: always + sample: "scheduled" + auto_routed: + description: Indicates if the scan was automatically routed. + type: int + returned: always + sample: 0 + baseline_next_scan: + description: Indicates the next baseline scan if applicable. + type: str + returned: when applicable + sample: null + container_id: + description: Identifier of the container where the scan was configured. + type: str + returned: always + sample: "123456789" + creation_date: + description: UNIX timestamp when the scan was created. + type: int + returned: always + sample: 123456789 + custom_targets: + description: Custom targets specified for the scan. + type: str + returned: always + sample: "123456789" + default_permissions: + description: Default permissions applied to the scan. + type: int + returned: always + sample: 0 + description: + description: Description of the scan. + type: str + returned: when applicable + sample: null + emails: + description: Emails associated with notifications for the scan. + type: str + returned: when applicable + sample: null + enabled: + description: Indicates whether the scan is enabled. + type: bool + returned: always + sample: false + id: + description: Unique identifier of the scan. + type: int + returned: always + sample: 2203 + include_aggregate: + description: Indicates if aggregate data is included in the scan results. + type: bool + returned: always + sample: true + interval_type: + description: Type of interval for recurring scans. + type: str + returned: when applicable + sample: null + interval_value: + description: Value of the interval for recurring scans. + type: str + returned: when applicable + sample: null + last_modification_date: + description: UNIX timestamp when the scan was last modified. + type: int + returned: always + sample: 123456789 + name: + description: Name of the scan. + type: str + returned: always + sample: "123456789" + notification_filters: + description: Filters applied to notifications for the scan. + type: str + returned: when applicable + sample: null + owner: + description: Email of the owner of the scan. + type: str + returned: always + sample: "autobit@nttdata.com" + owner_id: + description: User ID of the scan owner. + type: int + returned: always + sample: 2308677 + owner_uuid: + description: UUID of the scan owner. + type: str + returned: always + sample: "123456789" + policy_id: + description: Identifier of the policy applied to the scan. + type: int + returned: always + sample: 2202 + remediation: + description: Indicates if remediation is enabled for the scan. + type: int + returned: always + sample: 0 + reporting_mode: + description: Reporting mode for the scan. + type: str + returned: when applicable + sample: null + rrules: + description: Recurrence rules for scheduled scans. + type: str + returned: when applicable + sample: null + scan_time_window: + description: Time window for the scan. + type: int + returned: always + sample: 123456789 + scanner_id: + description: Identifier of the scanner used. + type: str + returned: when applicable + sample: null + scanner_uuid: + description: UUID of the scanner used. + type: str + returned: always + sample: "123456789" + shared: + description: Indicates if the scan is shared with other users. + type: int + returned: always + sample: 0 + sms: + description: SMS notifications for the scan. + type: str + returned: always + sample: "" + starttime: + description: Start time for the scan. + type: str + returned: when applicable + sample: null + tag_type: + description: Type of tags associated with the scan. + type: str + returned: when applicable + sample: null + target_network_uuid: + description: UUID of the network targeted by the scan. + type: str + returned: when applicable + sample: null + timezone: + description: Timezone in which the scan is scheduled. + type: str + returned: when applicable + sample: null + triggers: + description: Triggers that initiate the scan. + type: str + returned: when applicable + sample: null + type: + description: Type of scan, e.g., public or private. + type: str + returned: always + sample: "public" + user_permissions: + description: Permissions of the user on the scan. + type: int + returned: always + sample: 128 + uuid: + description: UUID of the scan. + type: str + returned: always + sample: "template-123456789" + version: + description: Version number of the scan setup. + type: int + returned: always + sample: 1 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_complex_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "uuid", "settings", "credentials", "plugin_configurations") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scans" + payload = build_complex_payload(module.params) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_scanner_group.py b/plugins/modules/create_scanner_group.py new file mode 100644 index 0000000..a9ff740 --- /dev/null +++ b/plugins/modules/create_scanner_group.py @@ -0,0 +1,118 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: create_scanner_group +short_description: Creates a new scanner group. +version_added: "0.0.1" +description: + - This module creates a new scanner group. + - You cannot use this endpoint to + Assign the new scanner group to a network object.able Vulnerability Management automatically assigns new scanner + groups to the default network object. To assign a scanner group to a network object, use the assign_scanners module. + - Configure scan routes for the scanner group. Instead, use the update_scan_routes module. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/scanner-groups-create . +options: + type: + description: + - The type of scanner group. If omitted, the default is load_balancing. + type: str + choices: ["load_balancing"] + required: false +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Create a new scanner group with default type + create_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" + type: "load_balancing" + +- name: Create a new scanner group with specified type using environment credentials + create_scanner_group: + name: "Example Group" + type: "load_balancing" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + creation_date: + description: Creation timestamp. + type: int + last_modification_date: + description: Last modification timestamp. + type: int + owner_id: + description: ID of the owner. + type: int + owner: + description: Name of the owner. + type: str + owner_uuid: + description: UUID of the owner. + type: str + default_permissions: + description: Default permissions. + type: int + scan_count: + description: Number of scans. + type: int + uuid: + description: UUID of the scanner group. + type: str + type: + description: Type of the scanner group. + type: str + name: + description: Name of the scanner group. + type: str + id: + description: ID of the scanner group. + type: int + owner_name: + description: Name of the owner. + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "name") + specific_spec = {"type": {"required": False, "type": "str", "choices": ["load_balancing"]}} + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scanner-groups" + + payload_keys = ["name", "type"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_tag_category.py b/plugins/modules/create_tag_category.py new file mode 100644 index 0000000..160c8b8 --- /dev/null +++ b/plugins/modules/create_tag_category.py @@ -0,0 +1,143 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_tag_category +short_description: Deletes the specified category and any associated tag values +version_added: "0.0.1" +description: + - This module Deletes the specified category and any associated tag values + - Deleting an asset tag category automatically deletes all tag values associated with that category + and removes the tags from any assets where the tags were assigned + - To delete a category you must be an admin or have edit permissions on all tags within the category. + - The module is made from https://developer.tenable.com/reference/tags-delete-tag-category docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + description: + description: + - The description of the tag category. + - Must not exceed 3000 characters. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "name" + description: "i am the description" + +- name: Create tag ceategory using enviroment creds + create_tag_category: + name: "name" +""" + +RETURN = r""" +api_response: + description: Detailed information about the API response. + returned: always + type: complex + contains: + data: + description: Data returned by the API, if any. + type: dict + returned: on success + contains: + created_at: + description: Timestamp when the tag category was created. + type: str + returned: always + sample: "123456" + created_by: + description: Email of the user who created the tag category. + type: str + returned: always + sample: "email@email.com" + description: + description: Description of the tag category. + type: str + returned: always + sample: "this is the description" + name: + description: Name of the tag category. + type: str + returned: always + sample: "name" + product: + description: Product associated with the tag category. + type: str + returned: always + sample: "1" + reserved: + description: Indicates if the category is reserved. + type: bool + returned: always + sample: false + updated_at: + description: Timestamp when the tag category was last updated. + type: str + returned: always + sample: "123456" + updated_by: + description: Email of the user who last updated the tag category. + type: str + returned: always + sample: "email@email.com" + uuid: + description: UUID of the tag category. + type: str + returned: always + sample: "123456" + sample: | + { + "data": { + "created_at": "123456", + "created_by": "email@email.com", + "description": "this is the description", + "name": "name", + "product": "1", + "reserved": false, + "updated_at": "123456", + "updated_by": "email@email.com", + "uuid": "123456" + }, + "status_code": 200 + } +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "name", "description") + argument_spec["name"]["required"] = True + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "tags/categories" + payload_keys = ["name", "description"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/create_tag_value.py b/plugins/modules/create_tag_value.py new file mode 100644 index 0000000..ddc3c83 --- /dev/null +++ b/plugins/modules/create_tag_value.py @@ -0,0 +1,164 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: create_tag_value +short_description: Creates a tag value. +version_added: "0.0.1" +description: + - This module creates a tag value. + - The tag category can be specified by UUID or name. + - If Tenable Vulnerability Management cannot find a category you specify by name, the system creates a new category with the specified name + - To automatically apply the tag to assets, specify the rules using the filters property. + - Module made from https://developer.tenable.com/reference/tags-create-tag-value docs. + - Requires SCAN MANAGER [40] user permissions +options: + category_name: + description: + - The name of the tag category to associate with the new value. + - Specify the name of a new category if you want to add both a new category and tag value. + - Specify the name of an existing category if you want to add the tag value to the existing category. + - The category_name can result in the following responses. + - If category_name and tag value exists a error 400 is returned. + - If category_name exists but not tag value, Tenable adds the tag value to the existing category. + - If category_name and tag value do not exist, Tenable creates a new tag category and adds the new tag value to that category. + type: str + required: false + category_uuid: + description: + - The UUID of the tag category to associate with the new value. + - This parameter is used to add the tag value to an existing category. If the UUID does not exist, a 400 error is returned. + - This parameter is required if category_name is not present in the request message. + type: str + required: false + category_description: + description: + - Description for a new tag category that is created if the category specified by name does not exist. + - Otherwise, Tenable Vulnerability Management ignores the description + type: str + required: false + value: + description: + - The new tag value. Cannot exceed 50 characters in length and must not contain commas. + type: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.tag_value_args +""" + + +EXAMPLES = r""" +- name: Create tag value using enviroment creds, matching with a category + create_tag_value: + value: "name_value" + category_name: "name" + category_uuid: "12345" + +- name: Create tag value and apply them in the assets where aws_ec2_name is equal to jenkins + create_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value: "name_2" + category_name: "cat_1" + filters: + asset: + and: + - field: aws_ec2_name + operator: eq + value: jenkins + +- name: Create tag value aand apply ir to all assets that are licensed or their sysid is not equal to sysid123456 + create_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value: "name_2" + category_name: "cat_1" + filters: + asset: + or: + - field: is_was_licensed + operator: eq + value: true + - field: servicenow_sysid + operator: neq + value: sysid123456 +""" + + +RETURN = r""" +api_response: + description: Detailed information about the creation of the tag value. + returned: always + type: dict + contains: + data: + description: Contains details about the tag such as access controls, metadata, and properties related to the tag's category. + type: dict + returned: always + sample: + access_control: + current_user_permissions: ["permission1", "permission2"] + category_description: "this is the description" + category_name: "name" + category_uuid: "123456" + consecutive_error_count: 0 + created_at: "date" + created_by: "email@email.com" + filters: + asset: "{\"and\": [{\"value\": [\"name_asset\"], \"operator\": \"eq\", \"property\": \"aws_ec2_name\"}]}" + product: "1" + saved_search: false + type: "dynamic" + updated_at: "date" + updated_by: "name@name.com" + uuid: "123456" + value: "use" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_repeated_special_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_tag_values_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec( + "access_key", + "secret_key", + "category_name", + "category_uuid", + "category_description", + "value", + "description", + "access_control", + "", + ) + special_spec = get_repeated_special_spec("filters", "value") + argument_spec = {**common_spec, **special_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "tags/values" + payload = build_tag_values_payload(module.params) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_agent_exclusion.py b/plugins/modules/delete_agent_exclusion.py new file mode 100644 index 0000000..2018db5 --- /dev/null +++ b/plugins/modules/delete_agent_exclusion.py @@ -0,0 +1,56 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_agent_exclusion +short_description: Deletes an agent exclusion. +version_added: "0.0.1" +description: + - This module deletes an agent exclusion. + - The module is made from https://developer.tenable.com/reference/agent-exclusions-delete docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.exclusion +""" + +EXAMPLES = r""" +- name: Delete exclusion + delete_agent_exclusion: + exclusion_id: 12345 + +- name: Delete exclusion + delete_agent_exclusion: + access_key: "your_access_key" + secret_key: "your_secret_key" + exclusion_id: 12345 +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "exclusion_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agents/exclusions/{module.params['exclusion_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_agent_group.py b/plugins/modules/delete_agent_group.py new file mode 100644 index 0000000..0510966 --- /dev/null +++ b/plugins/modules/delete_agent_group.py @@ -0,0 +1,64 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_agent_group +short_description: Deletes an agent group. +version_added: "0.0.1" +description: + - This module deletes an agent group. + - The module is made from https://developer.tenable.com/reference/agent-groups-delete docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Create agent group using enviroment creds + delete_agent_group: + group_id: 123456 + +- name: Create agent group passing creds as vars + delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "123456" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agent-groups/{module.params['group_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_an_exclusion.py b/plugins/modules/delete_an_exclusion.py new file mode 100644 index 0000000..3d5e2bf --- /dev/null +++ b/plugins/modules/delete_an_exclusion.py @@ -0,0 +1,64 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_an_exclusion +short_description: Deletes an exclusion. +version_added: "0.0.1" +description: + - This module deletes an exclusion. + - The module is made from https://developer.tenable.com/reference/exclusions-delete docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.exclusion +""" + +EXAMPLES = r""" +- name: Delete exclusion + delete_an_exclusion: + exclusion_id: 12345 + +- name: Delete exclusion + delete_an_exclusion: + access_key: "your_access_key" + secret_key: "your_secret_key" + exclusion_id: 12345 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "exclusion_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"exclusions/{module.params['exclusion_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_asset.py b/plugins/modules/delete_asset.py new file mode 100644 index 0000000..46f7192 --- /dev/null +++ b/plugins/modules/delete_asset.py @@ -0,0 +1,73 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: delete_asset +short_description: Deletes the specified asset. +version_added: "0.0.1" +description: + - This module fetches detailed information about a specific asset. + - When you delete an asset, Tenable Vulnerability Management deletes vulnerability data associated with the asset. + - Deleting an asset does not immediately subtract the asset from your licensed assets count. + - Deleted assets continue to be included in the count until they automatically age out as inactive. + - The module is made from https://developer.tenable.com/reference/workbenches-assets-delete docs. + - For information and best practices for retrieving vulnerability see https://developer.tenable.com/docs/retrieve-vulnerability-data-from-tenableio + - For information and best practices for retrieving assets see https://developer.tenable.com/docs/retrieve-asset-data-from-tenableio + - Note This endpoint is not intended for large or frequent exports of vulnerability or assets data. + - Requires SCAN OPERATOR [24] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset +""" + +EXAMPLES = r""" +- name: Delete an asset + delete_asset: + access_key: "your_access_key" + secret_key: "your_secret_key" + asset_uuid: 11111 + +- name: Delete an asset using enviroment creds + delete_asset: + asset_uuid: 11111 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"workbenches/assets/{module.params['asset_uuid']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_attribute.py b/plugins/modules/delete_attribute.py new file mode 100644 index 0000000..c4d9f02 --- /dev/null +++ b/plugins/modules/delete_attribute.py @@ -0,0 +1,64 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_attribute +short_description: Deletes the specified custom asset attribute. +version_added: "0.0.1" +description: + - This module deletes the specified custom asset attribute and removes it from all assets that it's assigned to. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/io-v3-asset-attributes-delete +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.attributes +""" + +EXAMPLES = r""" +- name: Delete a custom asset attribute + delete_attribute: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + attribute_id: "123" + +- name: Delete a custom asset attribute using enviroment creds + delete_attribute: + attribute_id: "123" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "attribute_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"api/v3/assets/attributes/{module.params['attribute_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_attribute_from_asset.py b/plugins/modules/delete_attribute_from_asset.py new file mode 100644 index 0000000..d8cc87a --- /dev/null +++ b/plugins/modules/delete_attribute_from_asset.py @@ -0,0 +1,68 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_attribute_from_asset +short_description: Deletes a single custom asset attribute from the specified asset. +version_added: "0.0.1" +description: + - This module deletes a single custom asset attribute from the specified asset. + - Module made from https://developer.tenable.com/reference/io-v3-asset-attributes-single-delete docs. + - Requires BASIC [16] user permissions +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset + - valkiriaaquatica.tenable.attributes +""" + + +EXAMPLES = r""" +- name: Update tag value + delete_attribute_from_asset: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "123456" + attribute_id: "987465" + +- name: Update tag value with envirometn creds and new filters + delete_attribute_from_asset: + asset_uuid: "123456" + attribute_id: "9874" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid", "attribute_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"api/v3/assets/{module.params['asset_uuid']}/attributes/{module.params['attribute_id']}" + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_attributes_from_asset.py b/plugins/modules/delete_attributes_from_asset.py new file mode 100644 index 0000000..9bca28d --- /dev/null +++ b/plugins/modules/delete_attributes_from_asset.py @@ -0,0 +1,65 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: delete_attributes_from_asset +short_description: Returns a list of custom asset attributes assigned to the specified asset. +version_added: "0.0.1" +description: + - This module deletes all custom asset attributes assigned to the specified asset. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-v3-asset-attributes-assigned-delete docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset +""" + +EXAMPLES = r""" +- name: List attributes assigned to asset + delete_attributes_from_asset: + access_key: "your_access_key" + secret_key: "your_secret_key" + asset_uuid: "12345" + +- name: List attributes assigned to asset using enviroment creds + delete_attributes_from_asset: + asset_uuid: "98746" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"api/v3/assets/{module.params['asset_uuid']}/attributes" + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_folder.py b/plugins/modules/delete_folder.py new file mode 100644 index 0000000..d027d90 --- /dev/null +++ b/plugins/modules/delete_folder.py @@ -0,0 +1,75 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_folder +short_description: Deletes a folder. +version_added: "0.0.1" +description: + - This module deletes a folder. + - If you delete a folder that contains scans, Tenable Vulnerability Management automatically moves those scans to the Trash folder + - You cannot delete Tenable-provided folders or custom folders that belong to other users + - The module is made from https://developer.tenable.com/reference/folders-delete docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + folder_id: + description: + - The ID of the folder to delete. + required: true + type: int +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Deletes a folder using enviroment creds + delete_folder: + folder_id: 12346 + +- name: Deletes a folder passing creds as vars + delete_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: 12346 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_repeated_special_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "folder_id") + special_spec = get_repeated_special_spec("folder_id") + argument_spec = {**common_spec, **special_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"folders/{module.params['folder_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_group.py b/plugins/modules/delete_group.py new file mode 100644 index 0000000..b83c92a --- /dev/null +++ b/plugins/modules/delete_group.py @@ -0,0 +1,64 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_group +short_description: Delete a group. +version_added: "0.0.1" +description: + - This module deletes a group in Tenable.io. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/groups-delete docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Delete a group + delete_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 1233 + +- name: Delete a group using enviroment creds + delete_group: + group_id: 1233 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"groups/{module.params['group_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_managed_credential.py b/plugins/modules/delete_managed_credential.py new file mode 100644 index 0000000..92a07cf --- /dev/null +++ b/plugins/modules/delete_managed_credential.py @@ -0,0 +1,69 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_managed_credential +short_description: Deletes the specified managed credential object. +version_added: "0.0.1" +description: + - This module deletes the specified managed credential object. + - When you delete a managed credential object, Tenable Vulnerability Management also + removes the credential from any scan that uses the credential. + - The module is made from https://developer.tenable.com/reference/credentials-delete docs. + - Requires CAN EDIT [64] credential permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.credential +""" + +EXAMPLES = r""" +- name: Deletes a manage credential using enviroment creds + delete_managed_credential: + credential_uuid: 12346 + +- name: Deletes a manage credential passing creds as vars + delete_managed_credential: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + credential_uuid: 12346 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "credential_uuid") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"credentials/{module.params['credential_uuid']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_network.py b/plugins/modules/delete_network.py new file mode 100644 index 0000000..b888f5d --- /dev/null +++ b/plugins/modules/delete_network.py @@ -0,0 +1,75 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: delete_network +short_description: Deletes the specified network object +version_added: "0.0.1" +description: + - This module deletes the specified network object + - Before you delete a network object, consider moving assets to a different network using the bulk asset move_assets module. + - You can view deleted network objects using the includeDeleted in the list_networks module. + - The module is made from https://developer.tenable.com/reference/networks-delete docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + network_id: + description: + - The id of the network to delete. + - Use the list_networks module to get the id. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Delete exclusion + delete_network: + network_id: 12345 + +- name: Delete exclusion + delete_network: + access_key: "your_access_key" + secret_key: "your_secret_key" + network_id: 12345 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "network_id") + argument_spec["network_id"]["required"] = True + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"networks/{module.params['network_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_scan.py b/plugins/modules/delete_scan.py new file mode 100644 index 0000000..334d730 --- /dev/null +++ b/plugins/modules/delete_scan.py @@ -0,0 +1,67 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_scan +short_description: Deletes a scan. +version_added: "0.0.1" +description: + - This module deletes a scan. + - You cannot delete scans with a running, paused, or stopping status. + - The module is made from https://developer.tenable.com/reference/scans-delete docs. + - Requires SCAN MANAGER [40] and CAN EDIT [64] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Delete scan using enviroment creds + delete_scan: + scan_id: 12345 + +- name: Delete exclusion + delete_scan: + access_key: "your_access_key" + secret_key: "your_secret_key" + scan_id: 12345 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scan_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_scan_history.py b/plugins/modules/delete_scan_history.py new file mode 100644 index 0000000..d1b4226 --- /dev/null +++ b/plugins/modules/delete_scan_history.py @@ -0,0 +1,106 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: delete_scan_history +short_description: Deletes historical results from a scan. +version_added: "0.0.1" +description: + - This module deletes historical results from a scan. Note that rollover scan data is also deleted. + - Scan details include information about when and where the scan ran, as well as the scan results for the target hosts. + - The module is made from https://developer.tenable.com/reference/scans-history-details + - Requires SCAN OPERATOR [24] and CAN VIEW [16] user permissions as specified in the Tenable.io API documentation. +options: + history_id: + description: + - The UUID of the historical scan result to return details about. + - This identifier corresponds to the history.id attribute of the response message from get_scan_history module or + the GET /scans/{scan_id}/history endpoint. + required: true + type: str + scan_id: + description: + - The UUID of the scan whose historical results you want to delete. + required: true + type: str + exclude_rollover: + description: + - Indicates whether or not to delete scan rollover history. + - If true, the scan history and its associated rollover scan data are deleted. + - If false or null and the scan has rollover data, you receive a 409 error + "This scan contains rollover scan data. If you want to delete it and all the associated rollover scan data, + use query parameter delete_rollovers=true." + required: false + type: bool +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Delete scan history details using enviroment creds + delete_scan_history: + scan_id: "123456" + history_uuid: "23545" + +- name: Get scan history details using limit and exclude_rollover + delete_scan_history: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" + history_uuid: "23545" +""" + + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id") + special_args = { + "history_id": { + "required": True, # different from arguments.py + "type": "str", + }, + "exclude_rollover": { + "required": False, + "type": "bool", + }, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if module.params["exclude_rollover"]: + endpoint = f"scans/{module.params['scan_id']}/history/{module.params['history_id']}?delete_rollovers={module.params['exclude_rollover']}" + else: + endpoint = f"scans/{module.params['scan_id']}/history/{module.params['history_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_scanner.py b/plugins/modules/delete_scanner.py new file mode 100644 index 0000000..90d2e4c --- /dev/null +++ b/plugins/modules/delete_scanner.py @@ -0,0 +1,66 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_scanner +short_description: Deletes and unlinks a scanner from Tenable Vulnerability Management. +version_added: "0.0.1" +description: + - This module deletes and unlinks a scanner from Tenable Vulnerability Management. + - The module is made from https://developer.tenable.com/reference/scanners-delete. + - Requires SCAN MANAGER [40] as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: Delete scan using enviroment creds + delete_scanner: + scanner_id: 12345 + +- name: Delete exclusion + delete_scanner: + access_key: "your_access_key" + secret_key: "your_secret_key" + scanner_id: 12345 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scanner_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/{module.params['scanner_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_scanner_group.py b/plugins/modules/delete_scanner_group.py new file mode 100644 index 0000000..5b22c4b --- /dev/null +++ b/plugins/modules/delete_scanner_group.py @@ -0,0 +1,65 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_scanner_group +short_description: Deletes a scanner group. +version_added: "0.0.1" +description: + - This module deletes a scanner group. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/scanner-groups-delete docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Delete a scanner group + delete_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 12345 + +- name: Delete a scanner group using environment credentials + delete_scanner_group: + group_id: 12345 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanner-groups/{module.params['group_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_tag_category.py b/plugins/modules/delete_tag_category.py new file mode 100644 index 0000000..0894810 --- /dev/null +++ b/plugins/modules/delete_tag_category.py @@ -0,0 +1,76 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_tag_category +short_description: Deletes the specified category and any associated tag values. +version_added: "0.0.1" +description: + - This module Deletes the specified category and any associated tag values + - Deleting an asset tag category automatically deletes all tag values associated with that category + and removes the tags from any assets where the tags were assigned + - To delete a category you must be an admin or have edit permissions on all tags within the category. + - The module is made from https://developer.tenable.com/reference/tags-delete-tag-category docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + category_uuid: + description: + - The UUID of the category to delete. + - For more information on determining this value, see https://developer.tenable.com/docs/determine-tag-identifiers-tio. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + + +EXAMPLES = r""" +- name: Delete tag category using creds as vars + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + +- name: Delete tag category using enviroment creds + delete_tag_category: + category_uuid: "{{ tag_category_uuid }}" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "category_uuid") + argument_spec["category_uuid"]["required"] = True + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"tags/categories/{module.params['category_uuid']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_tag_value.py b/plugins/modules/delete_tag_value.py new file mode 100644 index 0000000..79b706d --- /dev/null +++ b/plugins/modules/delete_tag_value.py @@ -0,0 +1,79 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_tag_value +short_description: Deletes the specified tag value. +version_added: "0.0.1" +description: + - This module deletes an agent group. + - If you delete an asset tag, Tenable Vulnerability Management also removes that tag from any assets where the tag was assigned. + - Note If you delete all asset tags associated with a category, Tenable Vulnerability Management retains the category. + - You must delete the category separately with delete_tag_category module or https://developer.tenable.com/reference/tags-delete-tag-category . + - Additionally, to delete a tag value, you must be an admin or have edit permissions on the tag value. + - The module is made from https://developer.tenable.com/reference/tags-delete-tag-value docs. + - Requires SCAN OPERATOR [24] user permissions as specified in the Tenable.io API documentation. +options: + value_uuid: + description: + - The UUID of the tag value you want to delete. + - A tag UUID is technically assigned to the tag value only (the second half of the category:value pair). + - But the API commands use this value to represent the whole category:value pair + - For more information on determining this value, see https://developer.tenable.com/docs/determine-tag-identifiers-tio + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Delete tag value + delete_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid }}" + +- name: Delete tag value using enviroment creds + delete_tag_value: + value_uuid: "123456" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = {"value_uuid": {"required": True, "type": "str"}} # unique for this module + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"tags/values/{module.params['value_uuid']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/delete_user_from_group.py b/plugins/modules/delete_user_from_group.py new file mode 100644 index 0000000..f18abc8 --- /dev/null +++ b/plugins/modules/delete_user_from_group.py @@ -0,0 +1,67 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: delete_user_from_group +short_description: Delete a user from the group. +version_added: "0.0.1" +description: + - This module deletes a user from a specified group in Tenable.io. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/groups-delete-user docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group + - valkiriaaquatica.tenable.user +""" + +EXAMPLES = r""" +- name: Delete a user from a group using explicit credentials + delete_user_from_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 123 + user_id: 433 + +- name: Delete a user from a group using environment creds + delete_user_from_group: + group_id: 123 + user_id: 433 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "user_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"groups/{module.params['group_id']}/users/{module.params['user_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/download_exported_scan.py b/plugins/modules/download_exported_scan.py new file mode 100644 index 0000000..8935b9b --- /dev/null +++ b/plugins/modules/download_exported_scan.py @@ -0,0 +1,106 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: download_exported_scan +short_description: Download an exported scan. +version_added: "0.0.1" +description: + - This module download an exported scan. + - The module is made from https://developer.tenable.com/reference/scans-export-download docs. + - Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions user permissions as specified in the Tenable.io API documentation. +options: + scan_id: + description: + - The unique identifier for the exported scan you want to download. + - This identifier can be either the scans.schedule_uuid or the scans.id attribute in + the response message from the list_scans module. Tenable recommends that you use scans.schedule_uuid + required: true + type: str + file_id: + description: + - The ID of the file to download (Included in response from /scans/{scan_id}/export). + required: true + type: str + download_path: + description: + - The path where the downloaded exported scan should be saved and the name. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Download exported scan using enviroment creds + download_exported_scan: + scan_id: "1234" + file_id: "wedcdfgdsfr-csv" + download_path: "/tmp/descarga.csv" + +- name: Download exported scan using enviroment creds + download_exported_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "1234" + file_id: "wedcdfgdsfr-csv" + download_path: "/tmp/descarga.csv" +""" + +RETURN = r""" +""" + + +import os + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api import TenableAPI +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import TenableAPIError + +from ansible.module_utils.basic import AnsibleModule + +# this module does not follow the same "skeleton" of the post, get, put and patch modules + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "scan_id": {"required": True, "type": "str"}, + "file_id": {"required": True, "type": "str"}, + "download_path": {"required": True, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/export/{module.params['file_id']}/download" + download_path = module.params["download_path"] + + if not os.path.isdir(os.path.dirname(download_path)): + module.fail_json(msg=f"The specified directory does not exist: {os.path.dirname(download_path)}") + + try: + tenable_api = TenableAPI(module) + tenable_api.headers["Accept"] = "application/octet-stream" + response = tenable_api.request("GET", endpoint) + + with open(download_path, "wb") as f: + f.write(response["data"]) + + module.exit_json(changed=True, msg="Scan downloaded successfully", path=download_path) + except TenableAPIError as e: + module.fail_json(msg=str(e), status_code=getattr(e, "status_code", "Unknown")) + except Exception as e: + module.fail_json(msg=f"An unexpected error occurred: {str(e)}") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/download_nessus_agent.py b/plugins/modules/download_nessus_agent.py new file mode 100644 index 0000000..e970aef --- /dev/null +++ b/plugins/modules/download_nessus_agent.py @@ -0,0 +1,239 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: download_nessus_agent +short_description: Downloads the specified version of the Nessus Agent. +version_added: "0.0.1" +description: + - This module downloads a specified version of the Nessus Agent for a given OS distribution and version. + - It can automatically fetch the latest version of the Nessus Agent if no specific version is provided. + - Module made from https://developer.tenable.com/reference/get_pages-slug-files-file docs. + - No authorization is required. +options: + os_distribution: + description: + - The target OS distribution for the Nessus Agent. + - Supported distributions include Amazon, CentOS, 'OracleLinux', 'Debian', 'RedHat', 'SLES', 'Ubuntu', and 'Windows'. + required: true + type: str + os_version: + description: + - The version of the OS distribution. Not required for distributions like 'Debian', 'Ubuntu', + and 'Windows'. For others, it specifies the version, example, '7' for CentOS 7. + required: false + type: str + default: 'default' + nessus_agent_version: + description: + - The version of the Nessus Agent to download. If not specified, the module will automatically determine the latest version available. + required: false + type: str + dest: + description: + - The destination path on the local machine where the Nessus Agent package will be saved. PLeaes include two slashes. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +""" + +EXAMPLES = r""" +- name: Download latest nessus agent in Linux gathering facts, this is the recomendes way of using, in localhost + download_nessus_agent: + os_distribution: "{{ ansible_facts['distribution'] }}" + os_version: "{{ ansible_facts['lsb']['major_release'] }}" + dest: "/tmp/" + delegate_to: localhost + + +- name: Download latest Nessus Agent for Ubuntu + download_nessus_agent: + os_distribution: "Ubuntu" + dest: "/path/to/download/" + +- name: Download latest nessus agent for AmazonLinux. + download_nessus_agent: + os_distribution: "Amazon" + dest: "/tmp/" + +- name: Download Nessus Agent version for RedHat 7 + download_nessus_agent: + os_distribution: "RedHat" + os_version: "7" + nessus_agent_version: "8" + dest: "/path/to/download/" + +- name: Download Nessus Agent version for CentOS 7 + download_nessus_agent: + os_distribution: "CentOS" + os_version: "7" + dest: "/tmp/" + +- name: Download latest Nessus Agent for Windows + download_nessus_agent: + os_distribution: "Windows" + dest: "C:\\path\\to\\download\\" + + +# this is an exmaple of how to downlaod and install nessus agent in linux server +- name: Ensure requests module exists in the target machine + ansible.builtin.command: pip install requests + +- name: Download latest nessus agent and saved it in /tmp directory + download_nessus_agent: + os_distribution: "{{ ansible_facts['distribution'] }}" + os_version: "{{ ansible_facts['lsb']['major_release'] }}" + dest: "/tmp/" + register: nessu_agent_download + +- name: Get the package name just downloaded + ansible.builtin.set_fact: + nessus_agent_filename: "{{ nessu_agent_download.filename }}" + +- name: Install agent Nessus Agent + apt: + deb: "{{ nessus_agent_filename }}" + state: present + register: install_result + become: true + +- name: Verify package was well installed + ansible.builtin.assert: + that: + - install_result.changed +""" + +RETURN = r""" +filename: + description: The name of the file downloaded. + type: str + returned: on sucess +msg: + description: Debug of imrmation + type: str + returned: on sucess +changed: + description: A boolean that indicates if there was a change in the state of the target. + returned: always + type: bool + sample: false +""" + +# the logic of this module is not related with the rest of moedules + +from ansible.module_utils.basic import AnsibleModule + +try: + import requests +except ImportError: + pass + + +def get_latest_nessus_version(): + url = "https://www.tenable.com/downloads/api/v2/pages/nessus-agents" + response = requests.get(url) + if response.status_code == 200: + data = response.json() + for release_name, files in data["releases"]["latest"].items(): + if "Nessus Agents - " in release_name: + return release_name.split(" - ")[1] + return "latest" + + +def get_filename(os_distribution, os_version, nessus_agent_version=None): + if not nessus_agent_version: + nessus_agent_version = get_latest_nessus_version() + # this may be updated + os_dict = { + "Amazon": { + "default": f"NessusAgent-{nessus_agent_version}-amzn2.x86_64.rpm", + }, + "CentOS": { + "7": f"NessusAgent-{nessus_agent_version}-el7.x86_64.rpm", + "8": f"NessusAgent-{nessus_agent_version}-el8.x86_64.rpm", + }, + "OracleLinux": { + "7": f"NessusAgent-{nessus_agent_version}-el7.x86_64.rpm", + "8": f"NessusAgent-{nessus_agent_version}-el8.x86_64.rpm", + }, + "Debian": { + "default": f"NessusAgent-{nessus_agent_version}-debian10_amd64.deb", + }, + "RedHat": { + "6": f"NessusAgent-{nessus_agent_version}-el6.x86_64.rpm", + "7": f"NessusAgent-{nessus_agent_version}-el7.x86_64.rpm", + "8": f"NessusAgent-{nessus_agent_version}-el8.x86_64.rpm", + "9": f"NessusAgent-{nessus_agent_version}-el9.x86_64.rpm", + }, + "SLES": { + "12": f"NessusAgent-{nessus_agent_version}-suse12.x86_64.rpm", + "15": f"NessusAgent-{nessus_agent_version}-suse15.x86_64.rpm", + }, + "Ubuntu": { + "default": f"NessusAgent-{nessus_agent_version}-ubuntu1404_amd64.deb", + }, + "Windows": { + "default": f"NessusAgent-{nessus_agent_version}-x64.msi", + }, + } + + if os_distribution in ["Ubuntu", "Debian", "Windows"]: + filename_template = os_dict[os_distribution]["default"] + else: + filename_template = os_dict.get(os_distribution, {}).get( + os_version, os_dict.get(os_distribution, {}).get("default") + ) + + if filename_template: + return filename_template.format(nessus_agent_version=nessus_agent_version) + + return None + + +def main(): + argument_spec = { + "os_distribution": {"required": True, "type": "str"}, + "os_version": {"required": False, "type": "str", "default": "default"}, + "nessus_agent_version": {"required": False, "type": "str", "default": None}, + "dest": {"required": True, "type": "str"}, + } + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=False, + ) + + os_distribution = module.params["os_distribution"] + os_version = module.params["os_version"] + nessus_agent_version = module.params["nessus_agent_version"] + dest = module.params["dest"] + + filename = get_filename(os_distribution, os_version, nessus_agent_version) + + if filename: + base_url = "https://www.tenable.com/downloads/api/v2/pages/nessus-agents/files/" + full_url = base_url + filename + response = requests.get(full_url, stream=True) + + if response.status_code == 200: + file_path = filename + with open(dest + filename, "wb") as file: + for chunk in response.iter_content(chunk_size=8192): + file.write(chunk) + module.exit_json(changed=True, filename=file_path, msg=f"File: {filename} download sucessefully.") + else: + module.fail_json(msg=f"Error downloading the file. Status code: {response.status_code}") + else: + module.fail_json(msg=f"File was not found for distribution: {os_distribution} with version {os_version}.") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/download_report.py b/plugins/modules/download_report.py new file mode 100644 index 0000000..6ef5122 --- /dev/null +++ b/plugins/modules/download_report.py @@ -0,0 +1,111 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: download_report +short_description: Downloads the specified PDF report. +version_added: "0.0.1" +description: + - Downloads the specified PDF report. + - The module is made from https://developer.tenable.com/reference/vm-reports-download docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + report_uuid: + description: + - The UUID of the report to download. + required: true + type: str + download_path: + description: + - The path where the downloaded PDF report should be saved and the name. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +RETURN = r""" +path: + description: The path where the downloaded PDF report is saved. + returned: always + type: str + sample: "/path/to_save/report.pdf" +size: + description: The size of the downloaded PDF report in bytes. + returned: always + type: int + sample: 46938 +msg: + description: A message indicating the status of the download. + returned: always + type: str + sample: "Report downloaded successfully" +""" + +EXAMPLES = r""" +- name: Download a report + download_report: + access_key: "your_access_key" + secret_key: "your_secret_key" + report_uuid: "123456" + download_path: "/tmp/report.pdf" + +- name: Download a report using enviroment creds + download_report: + report_uuid: "123456" + download_path: "/tmp/report.pdf" +""" + +import os + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api import TenableAPI +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import TenableAPIError + +from ansible.module_utils.basic import AnsibleModule + +# this module does not follow the same "skeleton" of the post, get, put and patch modules + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "report_uuid": {"required": True, "type": "str"}, + "download_path": {"required": True, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"reports/export/{module.params['report_uuid']}/download" + download_path = module.params["download_path"] + + if not os.path.isdir(os.path.dirname(download_path)): + module.fail_json(msg=f"The specified directory does not exist: {os.path.dirname(download_path)}") + + try: + tenable_api = TenableAPI(module) + tenable_api.headers["Accept"] = "application/pdf" + response = tenable_api.request("GET", endpoint) + + with open(download_path, "wb") as f: + f.write(response["data"]) + + module.exit_json(changed=True, msg="Report downloaded successfully", path=download_path) + except TenableAPIError as e: + module.fail_json(msg=str(e), status_code=getattr(e, "status_code", "Unknown")) + except Exception as e: + module.fail_json(msg=f"An unexpected error occurred: {str(e)}") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/enable_schedule.py b/plugins/modules/enable_schedule.py new file mode 100644 index 0000000..9965afa --- /dev/null +++ b/plugins/modules/enable_schedule.py @@ -0,0 +1,97 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: enable_schedule +short_description: Enables or disables a scan schedule. +version_added: "0.0.1" +description: + - This module enables or disables a scan schedule. + - The module is made from https://developer.tenable.com/reference/scans-schedule docs. + - Requires SCAN OPERATOR [24] and CAN EXECUTE [32] user permissions as specified in the Tenable.io API documentation. +options: + enabled: + description: + - Enables or disables the scan schedule. + required: true + type: bool +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Enable the scan schedule using enviroment creds + enable_schedule: + scan_id: "123456" + enabled: true + +- name: Disable the scan schedule + enable_schedule: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + enabled: false +""" + + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable API. + type: dict + returned: always when a request is made, independent if it is correct or incorrect. + contains: + data: + description: Specific data content of the API response, including detailed settings and configurations of the scan. + type: dict + returned: always + sample: + agent_scan_launch_type: "scheduled" + control: true + enabled: true + rrules: "FREQ=WEEKLY" + schedule_uuid: "123456" + starttime: "12346" + tag_type: "main" + timezone: "Europe/Madrid" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id") + specific_spec = {"enabled": {"required": True, "type": "bool"}} + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/schedule" + + payload_keys = ["enabled"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/export_scan.py b/plugins/modules/export_scan.py new file mode 100644 index 0000000..48752eb --- /dev/null +++ b/plugins/modules/export_scan.py @@ -0,0 +1,192 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: export_scan +short_description: Export the specified scan. +description: + - This module exports a specified scan in Tenable using the scan ID and export options. + - To see the status of the requested export, check_scan_export_status. + - Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions. +version_added: "0.0.1" +options: + history_id: + description: + - The unique identifier of the historical data that you want Tenable Vulnerability Management to export. + - This identifier corresponds to the history.id attribute of the response message from the get_scan_history module. + - Note Unlike other export formats, the Nessus file format includes individual open port findings. + - This ensures you can still view open port findings in Tenable Security Center if your organization integrates + Tenable Vulnerability Management with Tenable Security Center. + - Note If you request a scan export in the nessus file format, but do not specify filters + for the export, Tenable Vulnerability Management truncates the plugins output data in + the export file at 5 MB or 5,000,000 characters, and appends TRUNCATED (bracketed by + three asterisks) at the end of the output in the export file. You can obtain the full + plugins output by exporting the scan in any other file format than nessus. + - Note Vulnerability findings with a first_observed date within the last 14 days are + marked in the exported results as being in the New state. Vulnerability findings with a + first_observed date older than 14 days are marked in the exported results as being in the Active state. + - The module is made from https://developer.tenable.com/reference/scans-export-request docs. + - Requires SCAN OPERATOR [24] user permissions and CAN VIEW [16] scan permissions as specified + in the Tenable.io API documentation. + required: false + type: str + format: + description: + - The file format to use (Nessus, PDF, or CSV). + - You can export scans in PDF format for up to 60 days. For scans that are older than 60 days, + only the Nessus and CSV formats are supported. + - Unlike other export formats, the Nessus file format includes individual open port findings. + - This ensures you can still view open port findings in Tenable Security Center if your organization integrates + Tenable Vulnerability Management with Tenable Security Center + required: true + type: str + choices: + - nessus + - pdf + - csv + chapters: + description: + - The chapters to include in the export. + - This parameter accepts a semi-colon delimited string comprised of some combination of the following options + vuln_hosts_summary, vuln_by_host, compliance_exec, remediations, vuln_by_plugin, compliance). + - Note This parameter is required for the PDF file format. + required: false + type: str + filters: + description: + - The name of the filter to apply to the exported scan report. + - You can find available filters by using the list_vulnerability_filters module. + required: false + type: list + elements: dict + suboptions: + property: + description: + - The property to filter the results by. + required: true + type: str + operator: + description: + - The comparison operator to apply to the filter. For example, eq, neq, gt, etc. + required: true + type: str + value: + description: + - The value to compare the given property to using the specified operator. + required: true + type: raw + asset_id: + description: + - The ID of the asset scanned. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan + - valkiriaaquatica.tenable.filter_search_type +""" + +EXAMPLES = r""" +- name: Export a scan with filters + valkiriaaquatica.tenable.export_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "1234" + history_id: 54321 + format: "nessus" + chapters: "1" + filter_search_type: "and" + filters: + - property: "plugin.id" + operator: "eq" + value: 12345 + asset_id: "123456789" + tags: export_scan +""" + +RETURN = r""" +api_response: + description: The full API response from Tenable. + returned: success + type: dict + sample: + status_code: 200 + data: + uuid: "e122345" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api import TenableAPI +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import TenableAPIError + +from ansible.module_utils.basic import AnsibleModule + + +def build_export_scan_payload(params): + payload = { + "format": params.get("format"), + "chapters": params.get("chapters"), + "filter.search_type": params.get("filter_search_type"), + "asset_id": params.get("asset_id"), + } + + filters = params.get("filters") + if filters: + for idx, filter_ in enumerate(filters): + prefix = f"filter.{idx}" + payload[f"{prefix}.filter"] = filter_["property"] + payload[f"{prefix}.quality"] = filter_["operator"] + payload[f"{prefix}.value"] = filter_["value"] + return payload + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id", "history_id", "filter_search_type") + specific_spec = { + "format": {"required": True, "type": "str", "choices": ["nessus", "pdf", "csv"]}, + "chapters": {"required": False, "type": "str"}, + "filters": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "property": {"required": True, "type": "str"}, + "operator": {"required": True, "type": "str"}, + "value": {"required": True, "type": "raw"}, + }, + }, + "asset_id": {"required": False, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + scan_id = module.params["scan_id"] + history_id = module.params.get("history_id") + endpoint = f"scans/{scan_id}/export" + if history_id: + endpoint += f"?history_id={history_id}" + + payload = build_export_scan_payload(module.params) + + try: + tenable_api = TenableAPI(module) + response = tenable_api.request("POST", endpoint, data=payload) + + module.exit_json(changed=True, api_response=response) + except TenableAPIError as e: + module.fail_json(msg=str(e), status_code=getattr(e, "status_code", "Unknown")) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/force_stop_scan.py b/plugins/modules/force_stop_scan.py new file mode 100644 index 0000000..3cb5698 --- /dev/null +++ b/plugins/modules/force_stop_scan.py @@ -0,0 +1,83 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: force_stop_scan +short_description: Force stops a scan. +version_added: "0.0.1" +description: + - This module force stops a scan. + - A force stop cancels all the scan's incomplete scan tasks and updates the scan status to aborted + - Tenable Vulnerability Management processes and indexes the completed scan tasks + - After you force stop a scan, Tenable recommends re-running the scan in its entirety to ensure total scan coverage. + - You can use the force stop endpoint to abort a stalled scan in the stopping or publishing status. + - This can be helpful when you need to abort a scan before a freeze window or before a subsequent scheduled scan begins. + - You can only force stop a scan that has a status of stopping or publishing + - The module is made from https://developer.tenable.com/reference/vm-scans-stop-force docs. + - Requires SCAN OPERATOR [24] and CAN EXECUTE [32] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Create folder using enviroment creds + force_stop_scan: + scan_id: "123456" + +- name: Create folder passing creds as vars + force_stop_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" +""" + + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable API. + type: dict + returned: on success + contains: + data: + description: Specific data content of the API response that was deleted. + type: dict + returned: always + sample: { + "uuid": "template-123456" + } + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scan_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/force-stop" + + run_module(module, endpoint, method="POST") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_agent_configuration.py b/plugins/modules/get_agent_configuration.py new file mode 100644 index 0000000..c42364c --- /dev/null +++ b/plugins/modules/get_agent_configuration.py @@ -0,0 +1,88 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_agent_configuration +short_description: Returns the configuration of agents associated with a specific scanner. +version_added: "0.0.1" +description: + - This module returns the configuration of agents associated with a specific scanner. + - gent configuration controls agent settings for global agent software update enablement and agent auto-expiration. + - The module is made from https://developer.tenable.com/reference/agent-config-details docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: Get agent configuratin + get_agent_configuration: + access_key: "your_access_key" + secret_key: "your_secret_key" + scanner_id: "123456" + tags: get_agent_configuration + +- name: Get agent configuratin using enviroment creds + get_agent_configuration: + scanner_id: "123456" + tags: get_agent_configuration +""" + +RETURN = r""" +api_response: + description: Contains the raw response from the Tenable API. + returned: success + type: dict + contains: + auto_unlink: + description: Auto unlink settings. + returned: success + type: dict + contains: + enabled: + description: Indicates if auto unlink is enabled. + type: bool + sample: true + expiration: + description: The expiration period for auto unlink. + type: int + sample: 180 + software_update: + description: Indicates if software update is enabled. + returned: success + type: bool + sample: true + hybrid_scanning_enabled: + description: Indicates if hybrid scanning is enabled. + returned: success + type: bool + sample: false +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scanner_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/{module.params['scanner_id']}/agents/config" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_agent_details.py b/plugins/modules/get_agent_details.py new file mode 100644 index 0000000..8703824 --- /dev/null +++ b/plugins/modules/get_agent_details.py @@ -0,0 +1,101 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_agent_details +short_description: Retrieve detailed information of a agent using its agent_id. +version_added: "0.0.1" +description: + - This module fetches detailed information about a specific agent. + - The module is made from https://developer.tenable.com/reference/agents-agent-info docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.agent +""" + +EXAMPLES = r""" +- name: Get information of a agent + get_agent_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + agent_id: 11111 + +- name: Get information of a agent using enviroment keys + get_agent_details: + agent_id: 11111 +""" + +RETURN = r""" +api_response: + description: Detailed information about the network. + type: dict + returned: always + contains: + data: + description: Detailed data about the machine including its network settings and specific attributes. + type: dict + returned: always + sample: + id: 1234567 + uuid: "the_uuid" + name: "name_machine" + platform: "WINDOWS" + distro: "win-x86-64" + ip: "10.55.0.254" + last_scanned: 123456789 + plugin_feed_id: "202312010556" + core_build: "7" + core_version: "10.4.4" + linked_on: 123456789 + last_connect: 123456789 + status: "off" + aws_instance_id: "i-123456" + aws_account_id: "123456789" + supports_remote_logs: false + network_uuid: "the_network_uuid" + network_name: "the_network_name" + remote_settings: + - name: "Minimum Health Update Interval" + setting: "min_agent_health_update_interval" + type: "integer" + description: "Specifies, in minutes, the minimum interval to update health events" + min: 60 + status: "current" + value: "60" + default: "60" + supports_remote_settings: true + restart_pending: false + health_events: [] + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "agent_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + run_module(module, "scanners/null/agents", module.params["agent_id"], method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_agent_exclusion_details.py b/plugins/modules/get_agent_exclusion_details.py new file mode 100644 index 0000000..f623468 --- /dev/null +++ b/plugins/modules/get_agent_exclusion_details.py @@ -0,0 +1,123 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: get_agent_exclusion_details +short_description: Returns details for the specified agent exclusion. +version_added: "0.0.1" +description: + - This module returns details for the specified agent exclusion. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/agent-exclusions-details docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.exclusion +""" + +EXAMPLES = r""" +- name: Get agent exclusion details + get_agent_exclusion_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + exclusion_id: 124234 + +- name: Get agent exclusion details using environment credentials + get_agent_exclusion_details: + exclusion_id: 124234 + register: exclusion_details +""" + +RETURN = r""" +api_response: + description: Contains the raw response from the Tenable API. + returned: success + type: dict + contains: + id: + description: The ID of the exclusion. + type: int + sample: 124234 + name: + description: The name of the exclusion. + type: str + sample: "Routers" + description: + description: The description of the exclusion. + type: str + sample: "Router scan exclusion" + creation_date: + description: The date when the exclusion was created. + type: int + sample: 1543541807 + last_modification_date: + description: The date when the exclusion was last modified. + type: int + sample: 1543541807 + schedule: + description: Schedule details for the exclusion. + type: dict + contains: + endtime: + description: The end time of the schedule. + type: str + sample: "2019-12-31 19:35:00" + enabled: + description: Indicates if the schedule is enabled. + type: bool + sample: true + rrules: + description: Recurrence rules for the schedule. + type: dict + contains: + freq: + description: Frequency of the recurrence. + type: str + sample: "DAILY" + interval: + description: Interval for the recurrence. + type: int + sample: 8 + byweekday: + description: Days of the week for the recurrence. + type: str + sample: "SU,MO" + bymonthday: + description: Days of the month for the recurrence. + type: int + sample: 9 + timezone: + description: Timezone for the schedule. + type: str + sample: "US/Pacific" + starttime: + description: The start time of the schedule. + type: str + sample: "2018-12-31 19:35:00" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "exclusion_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agents/exclusions/{module.params['exclusion_id']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_agent_group_details.py b/plugins/modules/get_agent_group_details.py new file mode 100644 index 0000000..53d534e --- /dev/null +++ b/plugins/modules/get_agent_group_details.py @@ -0,0 +1,156 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: get_agent_group_details +short_description: Gets details for the agent group. Returns a list of agents +version_added: "0.0.1" +description: + - This module retrieves a list of agents from a group and group information. + - Supporting complex filtering,wilcarding, sorting, limit. + - Module made from https://developer.tenable.com/reference/agent-groups-details docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.filter_type + - valkiriaaquatica.tenable.filters_wildcards + - valkiriaaquatica.tenable.generics +""" + + +EXAMPLES = r""" +- name: Get agents from group using enviroment creds + get_agent_group_details: + group_id: 123456 + +- name: Get agents from grup 123456 + get_agent_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 123456 + +- name: Get agents from group 123456 that in any of the values contain jenkins + get_agent_group_details: + group_id: 123456 + wildcard_text: jenkins + +- name: Get agents from group that in any of the fields contain git or jenkins limiting 2 agents + get_agent_group_details: + group_id: 123456 + wildcard_text: jenkins, git + limit: 2 + +- name: Get agents from group 123456 and filtering just the Linux + get_agent_group_details: + group_id: 123456 + filters: + - type: platform + operator: eq + value: LINUX +""" + +RETURN = r""" +api_response: + description: Detailed information about the network. + type: dict + returned: on success + contains: + data: + description: Detailed data about the machine including its network settings and specific attributes. + type: dict + returned: always + sample: + id: 1234567 + uuid: "the_uuid" + name: "name_machine" + platform: "WINDOWS" + distro: "win-x86-64" + ip: "10.55.0.254" + last_scanned: 123456789 + plugin_feed_id: "202312010556" + core_build: "7" + core_version: "10.4.4" + linked_on: 123456789 + last_connect: 123456789 + status: "off" + aws_instance_id: "i-123456" + aws_account_id: "123456789" + supports_remote_logs: false + network_uuid: "the_network_uuid" + network_name: "the_network_name" + remote_settings: + - name: "Minimum Health Update Interval" + setting: "min_agent_health_update_interval" + type: "integer" + description: "Specifies, in minutes, the minimum interval to update health events" + min: 60 + status: "current" + value: "60" + default: "60" + supports_remote_settings: true + restart_pending: false + health_events: [] + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_special_filter +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec( + "access_key", + "secret_key", + "group_id", + "filters", + "filter_type", + "limit", + "offset", + "sort", + "wildcard_text", + "wildcard_fields", + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + group_id = module.params["group_id"] + endpoint = f"scanners/null/agent-groups/{group_id}" + + def query_params(): + return add_custom_filters( + build_query_parameters( + filter_type=module.params["filter_type"], + wildcard_text=module.params["wildcard_text"], + wildcard_fields=module.params["wildcard_fields"], + limit=module.params["limit"], + offset=module.params["offset"], + sort=module.params["sort"], + ), + module.params["filters"], + handle_special_filter, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_asset_activity_log.py b/plugins/modules/get_asset_activity_log.py new file mode 100644 index 0000000..58a18f7 --- /dev/null +++ b/plugins/modules/get_asset_activity_log.py @@ -0,0 +1,102 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_asset_activity_log +short_description: Returns the activity log for the specified asset +version_added: "0.0.1" +description: + - "This module returns the activity log for the specified asset." + - "Event types include the following:" + - "Discovered—Asset created (for example, by a network scan or import)." + - "Seen—Asset observed by a network scan without any changes to its attributes." + - "Tagging—Tag added to or removed from asset." + - "Attribute_change—A scan identified new or changed attributes for the asset (for example, new software applications installed on the asset)." + - "Updated—Asset updated either manually by a user or automatically by a new scan." + - "Note: This endpoint is not intended for large or frequent exports of vulnerability or assets data." + - "Tenable recommends the export_vulnerabilities module or https://developer.tenable.com/reference/exports-vulns-request-export for large amount." + - "For information and best practices for retrieving vulnerability see https://developer.tenable.com/docs/retrieve-vulnerability-data-from-tenableio." + - "For information and best practices for retrieving assets see https://developer.tenable.com/docs/retrieve-asset-data-from-tenableio." + - "Module made from https://developer.tenable.com/reference/workbenches-assets-activitydocs." + - "Requires BASIC [16] user permissions as specified in the Tenable.io API documentation." +author: + - "Fernando Mendieta Ovejero (@valkiriaaquatica)" +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset +""" + + +EXAMPLES = r""" +- name: Get asset activity log + get_asset_activity_log: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "123456" + +- name: Get asset activity log using enviroment creds + get_asset_activity_log: + asset_uuid: "123456" +""" + +RETURN = r""" +api_response: + description: Detailed information about the asset. + type: dict + returned: always + contains: + data: + description: Includes detailed activity data and match information for the asset. + type: dict + returned: always + sample: + activity: + - type: "seen" + timestamp: "date" + source: "discovery" + updates: [] + acceptedMatch: + matchingProperty: + propertyName: "aws_ec2_instance_id" + propertyValue: "i-123456" + updatedAt: "date" + rejectedMatch: + assetUuid: "123456" + contradictingProperty: + propertyName: "aws_ec2_instance_id" + propertyValue: + - "i-123456" + contradictingValue: + - "i-123456" + matchingProperty: + propertyName: "fqdn" + propertyValue: "123456" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid") + argument_spec["asset_uuid"]["required"] = True + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"workbenches/assets/{module.params['asset_uuid']}/activity" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_asset_details.py b/plugins/modules/get_asset_details.py new file mode 100644 index 0000000..698185b --- /dev/null +++ b/plugins/modules/get_asset_details.py @@ -0,0 +1,158 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_asset_details +short_description: Retrieve detailed information of a asset using its asset_uuid. +version_added: "0.0.1" +description: + - This module fetches detailed information about a specific asset. + - The module is made from https://developer.tenable.com/reference/assets-asset-info docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset +""" + +EXAMPLES = r""" +- name: Get information of a asset + get_asset_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + asset_uuid: 11111 + +- name: Get information of a asset using enviroment keys + get_asset_details: + asset_uuid: 11111 +""" + +RETURN = r""" +api_response: + description: Detailed information about the asset. + type: dict + returned: always + contains: + data: + description: Includes comprehensive details about the asset, such as identification, status, and network information. + type: dict + returned: always + sample: + id: "123456789" + has_agent: true + created_at: "2021-02-08T09:42:51.056Z" + updated_at: "2021-02-23T22:23:52.005Z" + terminated_at: null + deleted_at: null + first_seen: "2021-04-08T09:42:47.910Z" + last_seen: "2021-04-23T22:23:49.000Z" + last_scan_target: "192.168.1.11" + last_authenticated_scan_date: "2021-04-10T18:11:01.849Z" + last_licensed_scan_date: "2021-04-10T18:11:01.849Z" + last_scan_id: null + last_schedule_id: null + sources: + - name: "NESSUS_AGENT" + first_seen: "2021-04-08T09:42:47.910Z" + last_seen: "2021-04-23T09:48:44.998Z" + - name: "CloudDiscoveryConnector" + first_seen: "2021-04-08T11:10:16.000Z" + last_seen: "2021-04-23T22:23:49.000Z" + tags: + - tag_uuid: "thae_tag_uid_value" + tag_key: "Department" + tag_value: "Finance" + added_by: null + added_at: null + - tag_uuid: "thae_tag_uid_value" + tag_key: "Unavailable" + tag_value: "Unavailable" + added_by: null + added_at: null + acr_score: 4 + acr_drivers: null + exposure_score: 600 + scan_frequency: null + interfaces: + - ipv4: ["192.168.1.11"] + ipv6: [] + fqdn: ["i_am_fdqn"] + mac_address: ["i_am_mac_addres"] + name: "UNKNOWN" + virtual: null + aliased: null + network_id: ["i_am_network_id"] + ipv4: ["10.55.0.219"] + ipv6: [] + fqdn: ["i_am_fdqn"] + mac_address: ["i_am_mac_addres"] + netbios_name: [] + operating_system: ["Linux"] + system_type: [] + tenable_uuid: ["i_am_tenable_uuid"] + hostname: ["i_am_hostname"] + agent_name: ["i_am_name"] + bios_uuid: [] + aws_ec2_instance_id: ["i-123456789"] + aws_ec2_instance_ami_id: ["ami-123456789"] + aws_owner_id: ["123456789"] + aws_availability_zone: ["eu-west-1b"] + aws_region: ["eu-west-1"] + aws_vpc_id: ["i_am_aws_vpc_id"] + aws_ec2_instance_group_name: ["i_am_aws_ec2_instance_group_name"] + aws_ec2_instance_state_name: ["stopped"] + aws_ec2_instance_type: ["t2.micro"] + aws_subnet_id: ["subnet-02e977eb39cee9a25"] + aws_ec2_product_code: [] + aws_ec2_name: ["i_am_name"] + azure_vm_id: [] + azure_resource_id: [] + azure_subscription_id: [] + azure_resource_group: [] + azure_location: [] + azure_type: [] + gcp_project_id: [] + gcp_zone: [] + gcp_instance_id: [] + ssh_fingerprint: [] + mcafee_epo_guid: [] + mcafee_epo_agent_guid: [] + qualys_asset_id: [] + qualys_host_id: [] + servicenow_sysid: [] + installed_software: ["cpe:/a:amazon:amazon_ssm_agent:3.2.2303.0", "cpe:/a:java:jre:1.21.0_2"] + bigfix_asset_id: [] + security_protection_level: null + security_protections: [] + exposure_confidence_value: null + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + run_module(module, "assets", module.params["asset_uuid"], method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_asset_information.py b/plugins/modules/get_asset_information.py new file mode 100644 index 0000000..2932bca --- /dev/null +++ b/plugins/modules/get_asset_information.py @@ -0,0 +1,192 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_asset_information +short_description: Returns information about the specified asset. +version_added: "0.0.1" +description: + - This module returns information about the specified asset. + - Note This endpoint is not intended for large or frequent exports of vulnerability or assets data + - If you experience errors, reduce the volume, rate, or concurrency of your requests or narrow your filters. + - For information and best practices for retrieving assets see https://developer.tenable.com/docs/retrieve-asset-data-from-tenableio + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - The module is made from https://developer.tenable.com/reference/workbenches-asset-info docs. +options: + all_fields: + description: + - Specifies whether to include all fields ('full') or only the default fields ('default') in the returned data. + type: str + required: false + choices: ['full', 'default'] +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset +""" + +EXAMPLES = r""" +- name: Get asset information + get_asset_information: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "123456" + +- name: Get full asset information using enviroment creds + get_asset_information: + asset_id: "123456" + all_fields: full +""" + +RETURN = r""" +api_response: + description: Detailed information about the asset. + type: dict + returned: on success + contains: + data: + description: Includes comprehensive details about the asset including identification, security, and network information. + type: dict + returned: on success + sample: + acr_drivers: [] + acr_score: null + agent_name: ["agent_name"] + aws_availability_zone: ["eu-west-1a"] + aws_ec2_instance_ami_id: ["ami-123456"] + aws_ec2_instance_group_name: ["group_name"] + aws_ec2_instance_id: ["i-123456"] + aws_ec2_instance_state_name: ["running"] + aws_ec2_instance_type: ["t2.small"] + aws_ec2_name: ["aws_ec2_name"] + aws_ec2_product_code: [] + aws_owner_id: ["123456789"] + aws_region: ["eu-west-1"] + aws_subnet_id: ["subnet-123456789"] + aws_vpc_id: ["vpc-123456789"] + azure_location: [] + azure_resource_group: [] + azure_resource_id: [] + azure_subscription_id: [] + azure_type: [] + azure_vm_id: [] + bigfix_asset_id: [] + bios_uuid: [] + counts: + audits: + statuses: [{"count": 0, "level": 1, "name": "Passed"}] + total: 0 + vulnerabilities: + severities: [{"count": 0, "level": 1, "name": "Low"}] + total: 68 + created_at: "date" + deleted_at: null + exposure_confidence_value: 0 + exposure_score: null + first_seen: "date" + fqdn: ["fqdn_1", "fqdn_2"] + gcp_instance_id: [] + gcp_project_id: [] + gcp_zone: [] + has_agent: true + hostname: ["hostname"] + id: "123456789" + installed_software: ["cpe:/a:gnupg:libgcrypt:1.9.4"] + interfaces: + - fqdn: ["fqdn_1", "fqdn_2"] + ipv4: ["192.168.1.10", "10.10.10.15"] + ipv6: [] + mac_address: ["mac_address"] + name: "UNKNOWN" + ipv4: ["192.168.1.10", "10.10.10.15"] + ipv6: [] + last_authenticated_scan_date: "date" + last_licensed_scan_date: "date" + last_scan_id: null + last_scan_target: "192.168.1.20" + last_schedule_id: null + last_seen: "date" + mac_address: ["mac_address"] + mcafee_epo_agent_guid: [] + mcafee_epo_guid: [] + netbios_name: [] + network_name: "network_name" + operating_system: ["Linux"] + qualys_asset_id: [] + qualys_host_id: [] + scan_frequency: [] + security_protection_level: null + security_protections: [] + servicenow_sysid: [] + sources: + - first_seen: "date" + last_seen: "date" + name: "NESSUS_AGENT" + - first_seen: "date" + last_seen: "date" + name: "AWS" + - first_seen: "date" + last_seen: "date" + name: "name" + ssh_fingerprint: [] + system_type: ["x86_64"] + tags: + - added_at: "date" + source: "dynamic" + tag_key: "Cloud Provider" + tag_uuid: "123456" + tag_value: "AWS" + - added_at: "date" + source: "dynamic" + tag_key: "Department" + tag_uuid: "123456" + tag_value: "tag_value" + - added_at: "date" + added_by: "123456" + source: "static" + tag_key: "Project" + tag_uuid: "123456" + tag_value: "name" + tenable_uuid: ["123456789"] + time_end: null + time_start: "date" + updated_at: "date" + uuid: "123456" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid", "all_fields") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"workbenches/assets/{module.params['asset_id']}/info" + + def query_params(): + return build_query_parameters(all_fields=module.params["all_fields"]) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_asset_vulnerability_details.py b/plugins/modules/get_asset_vulnerability_details.py new file mode 100644 index 0000000..6662428 --- /dev/null +++ b/plugins/modules/get_asset_vulnerability_details.py @@ -0,0 +1,168 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_asset_vulnerability_details +short_description: Retrieves the details for a vulnerability recorded on a specified asset. +version_added: "0.0.1" +description: + - This module retrieves the details for a vulnerability recorded on a specified asset. + - Multiple filters can be applied and get full of default info rmo assets. + - Note This endpoint is not intended for large or frequent exports of vulnerability or assets data + - For information and best practices for retrieving vulnerability see https://developer.tenable.com/docs/retrieve-vulnerability-data-from-tenableio + - For information and best practices for retrieving assets see https://developer.tenable.com/docs/retrieve-asset-data-from-tenableio + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - This module was made from https://developer.tenable.com/reference/workbenches-asset-vulnerability-info docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset + - valkiriaaquatica.tenable.plugin + - valkiriaaquatica.tenable.date + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.filter_search_type +""" + + +EXAMPLES = r""" +- name: Get details of the plugin + get_asset_vulnerability_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "123456" + plugin_id: "987654" + +- name: Get details of the plugin using filters and enviroment creds in last five days + get_asset_vulnerability_details: + asset_id: "123456" + plugin_id: "987654" + date_range: 5 + filters: + - type: plugin.name + operator: match + value: RHEL +""" + +RETURN = r""" +api_response: + description: The API response containing a list of assets. + type: dict + returned: on success + contains: + data: + description: A list of assets retrieved from the API. + type: dict + contains: + assets: + description: Details of each asset including various descriptive and technical attributes. + type: list + elements: dict + sample: + - info: + accepted_count: 0 + count: 1 + description: "Nessus." + discovery: + seen_first: "date" + seen_last: "date" + plugin_details: + family: "Port scanners" + modification_date: "date" + name: "Netstat Portscanner (SSH)" + publication_date: "date" + severity: 0 + type: "local" + version: "1.101" + recasted_count: 0 + reference_information: [] + risk_information: + cvss3_base_score: null + cvss3_temporal_score: null + cvss3_temporal_vector: null + cvss3_vector: null + cvss_base_score: null + cvss_temporal_score: null + cvss_temporal_vector: null + cvss_vector: null + risk_factor: "INFO" + stig_severity: null + see_also: + - "https://en.wikipedia.org/wiki/Netstat" + severity: 0 + solution: null + state: "ACTIVE" + synopsis: "Remote open ports can be enumerated via SSH." + vpr: + drivers: + age_of_vuln: + lower_bound: null + cvss3_impact_score: null + cvss_impact_score_predicted: null + exploit_code_maturity: null + product_coverage: null + threat_intensity_last28: null + threat_sources_last28: null + score: null + updated: null + vuln_count: 1 + vulnerability_information: + asset_inventory: "False" + cpe: null + default_account: false + exploit_available: null + exploit_frameworks: [] + exploitability_ease: null + exploited_by_malware: false + exploited_by_nessus: false + in_the_news: false + malware: false + patch_publication_date: null + unsupported_by_vendor: false + vulnerability_publication_date: null + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec( + "access_key", "secret_key", "asset_uuid", "plugin_id", "date_range", "filters", "filter_search_type" + ) + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"workbenches/assets/{module.params['asset_id']}/vulnerabilities/{module.params['plugin_id']}/info" + + def query_params(): + return add_custom_filters( + build_query_parameters( + date_range=module.params["date_range"], filter_search_type=module.params["filter_search_type"] + ), + module.params["filters"], + handle_multiple_filters, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_category_details.py b/plugins/modules/get_category_details.py new file mode 100644 index 0000000..8d79f10 --- /dev/null +++ b/plugins/modules/get_category_details.py @@ -0,0 +1,84 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_category_details +short_description: Returns the details for the specified category. +version_added: "0.0.1" +description: + - This module returns the details for the specified category. + - The module is made from https://developer.tenable.com/reference/tags-tag-category-details docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.category +""" + +EXAMPLES = r""" +- name: Get tag categories details + get_category_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + +- name: Get tag categories details using enviroment creds + get_category_details: + category_uuid: "12345" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: always when a request is made, independent if it correct or incorrect. + contains: + data: + description: Details of the created or updated entity including metadata and identifiers. + type: dict + returned: always + sample: + created_at: "123456" + created_by: "email@email.com" + description: "this is the description" + name: "name" + product: "1" + reserved: false + updated_at: "123456" + updated_by: "email@email.com" + uuid: "123456" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "category_uuid") + argument_spec["category_uuid"]["required"] = True # in arguments is required False + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"tags/categories/{module.params['category_uuid']}" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_exclusion_details.py b/plugins/modules/get_exclusion_details.py new file mode 100644 index 0000000..4193469 --- /dev/null +++ b/plugins/modules/get_exclusion_details.py @@ -0,0 +1,89 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_exclusion_details +short_description: Returns exclusion details. +version_added: "0.0.1" +description: + - This module returns exclusion details. + - The module is made from https://developer.tenable.com/reference/exclusions-details docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.exclusion +""" + +EXAMPLES = r""" +- name: Get information of an exclusion + get_exclusion_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + exclusion_id: 11111 + +- name: Get information of an exclusion using enviroment keys + get_exclusion_details: + exclusion_id: 11111 +""" + + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: always when a request is made, independent if it correct or incorrect. + contains: + data: + description: Details of the exclusion including scheduling and network information. + type: dict + returned: always + sample: + creation_date: 123456 + description: "i am the exclusion" + id: 123456 + last_modification_date: 123456 + members: "192.168.1.120" + name: "exclusion" + network_id: "123456" + schedule: + enabled: true + endtime: "2023-04-01 17:00:00" + rrules: + bymonthday: null + byweekday: "MO,TU,WE,TH,FR" + freq: "WEEKLY" + interval: 1 + starttime: "2023-04-01 09:00:00" + timezone: "America/New_York" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "exclusion_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + run_module(module, "exclusions", module.params["exclusion_id"], method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_latest_scan_status.py b/plugins/modules/get_latest_scan_status.py new file mode 100644 index 0000000..0f3080e --- /dev/null +++ b/plugins/modules/get_latest_scan_status.py @@ -0,0 +1,78 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_latest_scan_status +short_description: Returns the latest status for a scan +version_added: "0.0.1" +description: + - This module returns the latest status for a scan + - For a list of possible status values, see https://developer.tenable.com/docs/scan-status-tio + - The module is made from https://developer.tenable.com/reference/scans-get-latest-status docs. + - Requires SCAN OPERATOR [24] and CAN VIEW [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Get latest scan status + get_latest_scan_status: + access_key: "your_access_key" + secret_key: "your_secret_key" + scan_id: "123456789" + +- name: Get latest scan status using enviroment creds + get_latest_scan_status: + scan_id: "123456789" +""" + + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: on success + contains: + data: + description: Contains information about the progress and status of the requested operation. + type: dict + returned: always + sample: + progress: 0 + status: "paused" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scan_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scans/{module.params['scan_id']}/latest-status" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_managed_credential_details.py b/plugins/modules/get_managed_credential_details.py new file mode 100644 index 0000000..1588a4f --- /dev/null +++ b/plugins/modules/get_managed_credential_details.py @@ -0,0 +1,143 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_managed_credential_details +short_description: Returns details of the specified managed credential object. +version_added: "0.0.1" +description: + - This module returns details of the specified managed credential object. + - The module is made from https://developer.tenable.com/reference/credentials-detailsdocs. + - Requires CAN USE [32] credential user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.credential +""" + +EXAMPLES = r""" +- name: Get managed credential details + get_managed_credential_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + credential_uuid: 11111 + +- name: Get managed credential details using enviroment keys + get_managed_credential_details: + credential_uuid: 11111 +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + name: + description: The name of the managed credential. + type: str + sample: "Windows devices (Headquarters)" + description: + description: The description of the managed credential. + type: str + sample: "Use for scans of Windows devices located at headquarters." + category: + description: The category of the managed credential. + type: dict + contains: + id: + description: The identifier for the category. + type: str + sample: "Host" + name: + description: The name of the category. + type: str + sample: "Host" + type: + description: The type of the managed credential. + type: dict + contains: + id: + description: The identifier for the type. + type: str + sample: "Windows" + name: + description: The name of the type. + type: str + sample: "Windows" + ad_hoc: + description: Indicates if the credential is ad-hoc. + type: bool + sample: false + user_permissions: + description: The user permissions for the credential. + type: int + sample: 64 + settings: + description: The settings for the credential. + type: dict + contains: + domain: + description: The Windows domain to which the username belongs. + type: str + sample: "" + username: + description: The username on the target system. + type: str + sample: "user@example.com" + auth_method: + description: The authentication method. + type: str + sample: "Password" + password: + description: The user password on the target system. + type: str + sample: "********" + permissions: + description: A list of user permissions for the managed credential. + type: list + elements: dict + contains: + grantee_uuid: + description: The UUID of the user or user group granted permissions for the managed credential. + type: str + sample: "59042c90-5379-43a2-8cf4-87d97f7cb68f" + type: + description: Specifies whether the grantee is a user or a user group. + type: str + sample: "user" + permissions: + description: Specifies the permissions granted to the user or user group for the credential. + type: int + sample: 64 + name: + description: The name of the user or user group granted permissions for the managed credential. + type: str + sample: "user1@tenable.com" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "credential_uuid") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"credentials/{module.params['credential_uuid']}" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_network_asset_count.py b/plugins/modules/get_network_asset_count.py new file mode 100644 index 0000000..a2f67d7 --- /dev/null +++ b/plugins/modules/get_network_asset_count.py @@ -0,0 +1,70 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_network_asset_count +short_description: Returns the assets in a network not seen in certain days. +version_added: "0.0.1" +description: + - This module Returns the total number of assets in the network along with the number of assets that have not been seen for the specified number of days. + - The module is made from https://developer.tenable.com/reference/io-networks-asset-count-details docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + num_days: + description: + - The number of days to get the assets. + required: true + type: int +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.network +""" + +EXAMPLES = r""" +- name: Get network asset count using enviromend creds + get_network_asset_count: + network_id: "123456" + num_days: 100 + +- name: Get network asset count using enviromend creds + get_network_asset_count: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "123456" + num_days: 100 +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "network_id") + common_spec["network_id"]["required"] = True + special_args = {"num_days": {"required": True, "type": "int"}} + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"networks/{module.params['network_id']}/counts/assets-not-seen-in/{module.params['num_days']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_network_details.py b/plugins/modules/get_network_details.py new file mode 100644 index 0000000..46cd3a2 --- /dev/null +++ b/plugins/modules/get_network_details.py @@ -0,0 +1,85 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_network_details +short_description: Retrieve detailed information of a network using its network_uuid. +version_added: "0.0.1" +description: + - This module fetches detailed information about a specific network. + - The module is made from https://developer.tenable.com/reference/networks-details docs. + - Requires ADMINSITRATOR [64] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.network +""" + +EXAMPLES = r""" +- name: Get information of a netowrk + get_network_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + network_id: 11111 + +- name: Get information of a netowrk using enviroment keys + get_network_details: + network_id: 11111 +""" + +RETURN = r""" +api_response: + description: Detailed information about the network. + type: dict + returned: always + contains: + data: + description: Contains key information about the network including creation and modification details. + type: dict + returned: always + sample: + created: 1689849698540 + created_by: "fdfsd-c9e7-4e1d-sdfef-sfrfds" + created_in_seconds: 1689849698 + is_default: false + modified: 1689849698540 + modified_by: "SFREVFGF6" + modified_in_seconds: 1689849698 + name: "network_name" + owner_uuid: "CRFRE" + scanner_count: 0 + uuid: "ssdfef-4f59-efgerg-b193-frtgtr" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_repeated_special_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + special_spec = get_repeated_special_spec("network_id") + + argument_spec = {**argument_spec, **special_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + run_module(module, "networks", module.params["network_id"], method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_plugin_details.py b/plugins/modules/get_plugin_details.py new file mode 100644 index 0000000..bb5ee30 --- /dev/null +++ b/plugins/modules/get_plugin_details.py @@ -0,0 +1,177 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: get_plugin_details +short_description: Retrieves the details for a plugin. +version_added: "0.0.1" +description: + - This module retrieves the details for a plugin. + - Note this endpoint is not intended for large or frequent exports of data. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - This module was made from https://developer.tenable.com/reference/workbenches-vulnerability-info docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.plugin + - valkiriaaquatica.tenable.date + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.filter_search_type +""" + +EXAMPLES = r""" +- name: Retrieve plugin details with specific filters + get_plugin_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + plugin_id: 123456 + filters: + - type: plugin.attributes.vpr.score + operator: gte + value: "6.5" + +- name: Get plugin details using environmental credentials + get_plugin_details: + plugin_id: 123456 +""" + +RETURN = r""" +api_response: + description: The API response containing plugin details. + returned: on success + type: complex + contains: + data: + description: Detailed information about the plugin. + type: dict + contains: + info: + description: Detailed description of the plugin. + type: dict + contains: + accepted_count: + description: The number of times the plugin's findings have been accepted. + type: int + count: + description: The count of vulnerabilities identified by this plugin. + type: int + description: + description: The description of the vulnerability. + type: str + discovery: + description: Discovery dates of the vulnerability. + type: dict + contains: + seen_first: + description: The first date the vulnerability was seen. + type: str + seen_last: + description: The most recent date the vulnerability was seen. + type: str + plugin_details: + description: Details about the plugin. + type: dict + contains: + family: + description: The family to which the plugin belongs. + type: str + modification_date: + description: The date when the plugin was last modified. + type: str + name: + description: The name of the plugin. + type: str + publication_date: + description: The publication date of the plugin. + type: str + severity: + description: The severity rating of the vulnerability. + type: int + type: + description: The type of the plugin. + type: str + version: + description: The version of the plugin. + type: str + risk_information: + description: Information about the risk associated with the vulnerability. + type: dict + contains: + cvss3_base_score: + description: The CVSS v3 base score of the vulnerability. + type: float + cvss3_temporal_score: + description: The CVSS v3 temporal score of the vulnerability. + type: float + cvss3_temporal_vector: + description: The CVSS v3 temporal vector of the vulnerability. + type: str + cvss3_vector: + description: The CVSS v3 vector of the vulnerability. + type: str + cvss_base_score: + description: The CVSS base score of the vulnerability. + type: float + cvss_temporal_score: + description: The CVSS temporal score of the vulnerability. + type: float + cvss_temporal_vector: + description: The CVSS temporal vector of the vulnerability. + type: str + cvss_vector: + description: The CVSS vector of the vulnerability. + type: str + risk_factor: + description: The risk factor of the vulnerability. + type: str + stig_severity: + description: The STIG severity of the vulnerability. + type: str + see_also: + description: Related links and references for further information. + type: list + elements: str +status_code: + description: HTTP status code returned by the API. + returned: always + type: int + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "plugin_id", "date_range", "filters", "filter_search_type") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + def query_params(): + return add_custom_filters( + build_query_parameters( + date_range=module.params["date_range"], filter_search_type=module.params["filter_search_type"] + ), + module.params["filters"], + handle_multiple_filters, + ) + + endpoint = f"workbenches/vulnerabilities/{module.params['plugin_id']}/info" + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_report_status.py b/plugins/modules/get_report_status.py new file mode 100644 index 0000000..b7eab08 --- /dev/null +++ b/plugins/modules/get_report_status.py @@ -0,0 +1,85 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_report_status +short_description: Returns the status of the specified report export request. +version_added: "0.0.1" +description: + - Returns the status of the specified report export request. + - The module is made from https://developer.tenable.com/reference/vm-reports-status docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + report_uuid: + description: + - The UUID of the report to check the status for.. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Get report status + get_report_status: + access_key: "your_access_key" + secret_key: "your_secret_key" + report_uuid: "123456" + +- name: Get report status using enviroment keys + get_report_status: + report_uuid: "987" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Data content of the response from the Tenable API. + type: dict + returned: on success + contains: + status: + description: The status of the report + type: str + sample: "COMPLETED" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + special_args = { + "report_uuid": {"required": True, "type": "str"}, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"reports/export/{module.params['report_uuid']}/status" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_scan_count.py b/plugins/modules/get_scan_count.py new file mode 100644 index 0000000..d385a74 --- /dev/null +++ b/plugins/modules/get_scan_count.py @@ -0,0 +1,73 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_scan_count +short_description: Returns scan results for a specific scan +version_added: "0.0.1" +description: + - Returns the total number of scans in your container. + - You can use the active query parameter to return only the number of active scans. + - The module is made from https://developer.tenable.com/reference/io-scans-count docs. + - Requires BASIC [16] and CAN VIEW [16] user permissions as specified in the Tenable.io API documentation. +options: + active: + description: + - The unique identifier of the historical data + - If true, only active scans are counted. + - If false, all active and inactive scans are counted. + - If this parameter is omitted, Tenable Vulnerability Management defaults to false. + required: false + type: bool +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Get scan count + get_scan_count: + access_key: "your_access_key" + secret_key: "your_secret_key" + +- name: Get scan count of active scans using enviroment keys + get_scan_count: + active: true +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + special_args = { + "active": {"required": False, "type": "bool", "default": None}, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + if module.params["active"] is not None: + endpoint = f"scans/count?active={module.params['active']}" + else: + endpoint = "scans/count" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_scan_details.py b/plugins/modules/get_scan_details.py new file mode 100644 index 0000000..41b992d --- /dev/null +++ b/plugins/modules/get_scan_details.py @@ -0,0 +1,73 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_scan_details +short_description: Returns scan results for a specific scan +version_added: "0.0.1" +description: + - This module returns scan results for a specific scan + - If you submit a request without query parameters, Tenable Vulnerability Management returns results from the latest run of the specified scan + - The module is made from https://developer.tenable.com/reference/scans-details docs. + - Requires SCAN MANAGER [24] and CAN VIEW [16] user permissions as specified in the Tenable.io API documentation. +options: + history_id: + description: + - The unique identifier of the historical data. + - Use https://developer.tenable.com/reference/scans-history to get the id or use the get_scan_history module. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Get scan details + get_scan_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + scanner_id: 11111 + +- name: Get scan details using enviroment keys + get_scan_details: + scanner_id: 11111 + history_id: "12345" +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scan_id", "history_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scans/{module.params['scan_id']}" + + def query_params(): + return build_query_parameters( + history_id=module.params["history_id"], + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_scan_history.py b/plugins/modules/get_scan_history.py new file mode 100644 index 0000000..fafd1c7 --- /dev/null +++ b/plugins/modules/get_scan_history.py @@ -0,0 +1,171 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_scan_history +short_description: Returns a list of objects. +version_added: "0.0.1" +description: + - This module returns a list of objects, each of which represent an individual run of the specified scan. + - You can only resume a scan that has a status of paused. + - The module is made from https://developer.tenable.com/reference/scans-history + - Requires SCAN OPERATOR [24] and CAN VIEW [16] user permissions as specified in the Tenable.io API documentation. +options: + exclude_rollover: + description: + - Indicates whether or not to exclude rollover scans from the scan history. + - If no value is provided for this parameter, Tenable Vulnerability Management uses the default value false + required: false + type: bool + default: False +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan + - valkiriaaquatica.tenable.generics + +""" + +EXAMPLES = r""" +- name: Get scan history using enviroment creds + get_scan_history: + scan_id: "123456" + +- name: Get scan history using limit and exclude_rollover + get_scan_history: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" + limit: 1 + exclude_rollover: true +""" + + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Data content of the response from the Tenable API. + type: dict + returned: always + contains: + history: + description: List of scan history objects. + type: list + returned: always + contains: + id: + description: Unique identifier for the scan history. + type: int + sample: 25655263 + is_archived: + description: Indicates if the scan history is archived. + type: bool + sample: false + reindexing: + description: Reindexing information. + type: dict + sample: null + reporting_mode: + description: Reporting mode of the scan. + type: str + sample: "baseline" + scan_uuid: + description: UUID of the scan. + type: str + sample: "122323" + status: + description: Status of the scan. + type: str + sample: "aborted" + targets: + description: Target information of the scan. + type: dict + contains: + custom: + description: Indicates if custom targets were used. + type: bool + sample: false + default: + description: Default target information. + type: dict + sample: null + time_end: + description: End time of the scan. + type: int + sample: 1715800542 + time_start: + description: Start time of the scan. + type: int + sample: 1715800525 + visibility: + description: Visibility of the scan. + type: str + sample: "public" + pagination: + description: Pagination information for the scan history. + type: dict + contains: + sort: + description: Sorting information. + type: list + contains: + name: + description: Name of the field used for sorting. + type: str + sample: "start_date" + order: + description: Order of sorting. + type: str + sample: "DESC" + total: + description: Total number of scan history objects. + type: int + sample: 2 + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id", "limit", "offset", "sort") + # exclude_rollover exclusive for this module + special_args = { + "exclude_rollover": { + "required": False, + "type": "bool", + "default": False, + }, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scans/{module.params['scan_id']}/history?exclude_rollover={module.params['exclude_rollover']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_scan_history_details.py b/plugins/modules/get_scan_history_details.py new file mode 100644 index 0000000..f9b1025 --- /dev/null +++ b/plugins/modules/get_scan_history_details.py @@ -0,0 +1,153 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_scan_history_details +short_description: Returns the details of a previous run of the specified scan. +version_added: "0.0.1" +description: + - This module returns the details of a previous run of the specified scan. + - Scan details include information about when and where the scan ran, as well as the scan results for the target hosts. + - The module is made from https://developer.tenable.com/reference/scans-history-details + - Requires SCAN OPERATOR [24] and CAN VIEW [16] user permissions as specified in the Tenable.io API documentation. +options: + history_uuid: + description: + - The UUID of the historical scan result to return details about. + - This identifier corresponds to the history.scan_uuid attribute of the response message from get_scan_history moddule or + the GET /scans/{scan_id}/history endpoint. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Get scan history details using enviroment creds + get_scan_history_details: + scan_id: "123456" + history_uuid: "23545" + +- name: Get scan history details using limit and exclude_rollover + get_scan_history_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" + history_uuid: "23545" +""" + + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Data content of the response from the Tenable API. + type: dict + returned: always + contains: + is_archived: + description: Indicates if the scan is archived. + type: bool + sample: true + name: + description: Name of the scan. + type: str + sample: "name" + object_id: + description: Unique identifier for the scan object. + type: int + sample: 24858000 + owner: + description: Owner of the scan. + type: str + sample: "" + owner_id: + description: Unique identifier for the owner. + type: int + sample: 2308677 + owner_uuid: + description: UUID of the owner. + type: str + sample: "6c9a7bf3-d5d5-4bb7-a756-02a388c309cf" + reindexing: + description: Reindexing information. + type: dict + sample: null + reporting_mode: + description: Reporting mode of the scan. + type: str + sample: null + scan_end: + description: End time of the scan. + type: int + sample: 1710511883 + scan_start: + description: Start time of the scan. + type: int + sample: 1710506677 + scan_type: + description: Type of the scan. + type: str + sample: "agent" + schedule_uuid: + description: UUID of the scan schedule. + type: str + sample: "1111" + status: + description: Status of the scan. + type: str + sample: "completed" + targets: + description: Targets of the scan. + type: str + sample: "127.0.0.1" + uuid: + description: UUID of the scan. + type: str + sample: "11111" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id") + special_args = { + "history_uuid": { + "required": True, + "type": "str", + }, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/history/{module.params['history_uuid']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_scan_progress.py b/plugins/modules/get_scan_progress.py new file mode 100644 index 0000000..f65b329 --- /dev/null +++ b/plugins/modules/get_scan_progress.py @@ -0,0 +1,102 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_scan_progress +short_description: Returns the progress for the specified scan. +version_added: "0.0.1" +description: + - This module returns the progress for the specified scan. + - Note If you submit a request without query parameters, Tenable Vulnerability Management + returns the progress from the latest run of the specified scan. + - If you submit a request using the history_id or history_uuid query parameters to specify + a historical run of the scan, Tenable Vulnerability Management returns the progress + for the specified historical run. + - The module is made from https://developer.tenable.com/reference/io-vm-scans-progress-get . + - Requires SCAN OPERATOR [24] and CAN VIEW [16] user permissions as specified in the Tenable.io API documentation. +options: + history_uuid: + description: + - The UUID of the historical scan result to return details about. + - This identifier corresponds to the history.scan_uuid attribute of the response message from get_scan_history moddule or + the GET /scans/{scan_id}/history endpoint. + required: false + type: str + history_id: + description: + - The unique identifier of the historical data. + - Use https://developer.tenable.com/reference/scans-history to get the id or use the get_scan_history module. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Get scan progress using enviroment creds + get_scan_progress: + scan_id: "123456" + +- name: Get scan progress using history_id + get_scan_progress: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" + history_id: 123 + +- name: Get scan progress using history_uuid + get_scan_progress: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" + history_uuid: "12345" +""" + + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id", "history_id") + special_args = { + "history_uuid": { + "required": False, + "type": "str", + }, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/progress" + query_params = [] + + if module.params.get("history_id"): + query_params.append(f"history_id={module.params['history_id']}") + if module.params.get("history_uuid"): + query_params.append(f"history_uuid={module.params['history_uuid']}") + + if query_params: + endpoint += "?" + "&".join(query_params) + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_scanner_details.py b/plugins/modules/get_scanner_details.py new file mode 100644 index 0000000..5f83b1e --- /dev/null +++ b/plugins/modules/get_scanner_details.py @@ -0,0 +1,99 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_scanner_details +short_description: Returns the scanner list. +version_added: "0.0.1" +description: + - This module returns the scanner list. + - The module is made from https://developer.tenable.com/reference/scanners-list docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: Get scanner details + get_scanner_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + scanner_id: 11111 + +- name: Get scanner details using enviroment keys + get_scanner_details: + scanner_id: 11111 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: on sucess + contains: + data: + description: Contains detailed information about an asset or group, including its status, ownership, and connection details. + type: dict + returned: always + sample: + creation_date: 123456 + group: true + id: 123456 + key: "123456" + last_connect: null + last_modification_date: 123456 + license: null + linked: 1 + name: "name" + network_name: "name" + num_scans: 0 + owner: "system" + owner_id: 123456 + owner_name: "system" + owner_uuid: "123456" + pool: true + scan_count: 0 + source: "service" + status: "on" + supports_remote_logs: false + supports_remote_settings: false + timestamp: 123456 + type: "local" + user_permissions: 64 + uuid: "123456" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scanner_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scanners/{module.params['scanner_id']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_scanner_key.py b/plugins/modules/get_scanner_key.py new file mode 100644 index 0000000..bedb5eb --- /dev/null +++ b/plugins/modules/get_scanner_key.py @@ -0,0 +1,70 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_scanner_key +short_description: Retrieves the key of the requested scanner. +version_added: "0.0.1" +description: + - This module retrieves the key of the requested scanner. + - The module is made from https://developer.tenable.com/reference/scanners-get-scanner-key docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: Get scanner key + get_scanner_key: + access_key: "your_access_key" + secret_key: "your_secret_key" + scanner_id: 11111 + +- name: Get scanner key using enviroment keys + get_scanner_key: + scanner_id: 11111 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: on success + contains: + key: + description: The scanner key + type: dict + returned: on success + sample: + key: "12345665898" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scanner_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scanners/{module.params['scanner_id']}/key" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_server_status.py b/plugins/modules/get_server_status.py new file mode 100644 index 0000000..70c8a50 --- /dev/null +++ b/plugins/modules/get_server_status.py @@ -0,0 +1,64 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: get_server_status +short_description: Gets the server status. +version_added: "0.0.1" +description: + - This module retrieves the server status from Tenable.io. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Get server status + get_server_status: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + code: + description: HTTP status code returned by the API. + type: int + sample: 200 + status: + description: Status of the server. + type: str + sample: "ready" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + + argument_spec = {**common_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "server/status" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_tag_value_details.py b/plugins/modules/get_tag_value_details.py new file mode 100644 index 0000000..87f1d3a --- /dev/null +++ b/plugins/modules/get_tag_value_details.py @@ -0,0 +1,101 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: get_tag_value_details +short_description: Returns the details for specified tag value. +version_added: "0.0.1" +description: + - This module returns the details for specified tag value. + - The module is made from https://developer.tenable.com/reference/tags-tag-value-details docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + value_uuid: + description: + - The UUID of the tag value. + - Use the list_tag_values module to list and get the id. + - For more information on determining this value, see https://developer.tenable.com/docs/determine-tag-identifiers-tio + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Get tag value details + get_tag_value_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid }}" + +- name: Get tag value details using enviroment creds + get_tag_value_details: + value_uuid: "123456" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: on success + contains: + data: + description: Contains detailed information about a category within the system, including metadata and access control settings. + type: dict + returned: on success + sample: + access_control: + current_user_permissions: ["CAN_USE", "CAN_EDIT"] + category_description: "this is the category_description" + category_name: "name" + category_uuid: "123456" + consecutive_error_count: 0 + created_at: "date" + created_by: "email@email.com" + description: "description i am" + product: "IO" + saved_search: false + type: "static" + updated_at: "date" + updated_by: "email@email.com" + uuid: "123456" + value: "value i'm" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "value_uuid": {"required": True, "type": "str"}, + } + + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"tags/values/{module.params['value_uuid']}" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_template_details.py b/plugins/modules/get_template_details.py new file mode 100644 index 0000000..f9a6f94 --- /dev/null +++ b/plugins/modules/get_template_details.py @@ -0,0 +1,71 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: get_template_details +short_description: Gets details for the specified template. +version_added: "0.0.1" +description: + - Gets details for the specified template. + - The module is made from https://developer.tenable.com/reference/editor-template-details docs. + - Requires STANDARD [32] user permissions as specified in the Tenable.io API documentation. +options: + wizard_uuid: + description: + - The UUID for the template to get details from. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan_templates +""" + +EXAMPLES = r""" +- name: Get scanner details + get_template_details: + access_key: "your_access_key" + secret_key: "your_secret_key" + type: "scan" + wizard_uuid: "12345789" + +- name: Get scanner details using enviroment keys + get_template_details: + scanner_id: 11111 + type: "remediation" + wizard_uuid: "987654" +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "type", "wizard_uuid") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if module.params["type"] == "scan": + endpoint = f"editor/scan/templates/{module.params['wizard_uuid']}" + if module.params["type"] == "policy": + endpoint = f"editor/policy/templates/{module.params['wizard_uuid']}" + if module.params["type"] == "remediation": + endpoint = f"editor/remediation/templates/{module.params['wizard_uuid']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/get_timezones.py b/plugins/modules/get_timezones.py new file mode 100644 index 0000000..b6a16ae --- /dev/null +++ b/plugins/modules/get_timezones.py @@ -0,0 +1,72 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: get_timezones +short_description: Lists all available time zones in Tenable. +version_added: "0.0.1" +description: + - This module retrieves a list of all available time zones from Tenable. + - Requires SCAN OPERATOR [24] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/scans-timezones docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List all available time zones in Tenable + get_timezones: + access_key: "your_access_key" + secret_key: "your_secret_key" +- name: List all available time zones in Tenable using envirometn credentials + get_timezones: + register: all_timezones +""" + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: Data containing the timezones. + type: dict + returned: always + contains: + timezones: + description: List of timezones. + type: list + returned: always + contains: + name: + description: The name identifier of the timezone. + type: str + returned: always + sample: "Africa/Abidjan" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + run_module(module, "scans/timezones", method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/launch_scan.py b/plugins/modules/launch_scan.py new file mode 100644 index 0000000..356a4ce --- /dev/null +++ b/plugins/modules/launch_scan.py @@ -0,0 +1,106 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: launch_scan +short_description: Launches a scan. +version_added: "0.0.1" +description: + - This module launches a scan. + - For more information of scans https://developer.tenable.com/docs/launch-scan-tio + - The module is made from https://developer.tenable.com/reference/scans-launch docs. + - There is a limit of 25 active scans per container. + - You can use use the get_scan_count endpoint to retrieve the total number of active scans in your container + - Requires SCAN OPERATOR [24] and CAN EXECUTE [32] user permissions as specified in the Tenable.io API documentation. +options: + alt_targets: + description: + - If you include this parameter, Tenable Vulnerability Management scans these targets instead of the default. + - Value can be an array where each index is a target, or an array with a single index of comma-separated targets. + required: false + type: list + elements: str + rollover: + description: + - Indicates whether or not to launch a rollover scan instead of full scan. + - A rollover scan only runs against the targets that Tenable Vulnerability Management did not scan due to a previous scan timeout. + required: false + type: bool +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Launch scan using enviroment creds + launch_scan: + scan_id: "123456" + alt_targets: + - "1.11.11.111" + - "2.22.22.2" + - "3.33.3.3" + +- name: Launch scan using enviroment creds + launch_scan: + access_key: "your_access_key" + secret_key: "your_secret_key" + scan_id: "123456" + alt_targets: "192.168.1.150,192.168.1.120" + rollover: true +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: always when a request is made, independent if it is correct or incorrect. + contains: + data: + description: Contains the scan UUID, a unique identifier for the scan operation performed. + type: dict + returned: always + sample: + scan_uuid: "123456789" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id") + specific_spec = { + "alt_targets": {"required": False, "type": "list", "elements": "str"}, + "rollover": {"required": False, "type": "bool"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/launch" + + payload_keys = ["alt_targets", "rollover"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/link_agent_linux.py b/plugins/modules/link_agent_linux.py new file mode 100644 index 0000000..881f058 --- /dev/null +++ b/plugins/modules/link_agent_linux.py @@ -0,0 +1,154 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: link_agent_linux +short_description: Link a Nessus Agent from a linux machine to Tenable.io. +version_added: "0.0.1" +description: + - This module links a Nessus Agent to Tenable.io by executing a command on the host machine. + - Module made from https://docs.tenable.com/nessus/Content/LinkAgenttoNessusManager.htm docs. + - No macOS module yet. +options: + linking_key: + description: + - The linking key provided by Tenable.io or Tenable.sc for the agent. + required: true + type: str + name: + description: + - The name to assign to the Nessus Agent. + required: true + type: str + groups: + description: + - The agent groups to assign the Nessus Agent to. + required: true + type: str + network: + description: + - The network address for Tenable.sc. + required: true + type: str + host: + description: + - The hostname or IP address of Tenable.io or Tenable.sc. + required: true + type: str + port: + description: + - The port number to communicate with Tenable.io or Tenable.sc. + required: true + type: int + become: + description: + - Whether to execute the linking command with elevated privileges (sudo). + required: false + type: bool + default: false +requirements: + - Python >= 3.0 +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +""" + +EXAMPLES = r""" +- name: Link Nessus Agent to Tenable.io + link_agent_linux: + linking_key: "{{ your_linking_key_here }}" + name: "{{ ansible_hostname }}" + groups: "{{ tenable_group }}" + network: "{{ tenable_network }}" + host: "sensor.cloud.tenable.com" + port: 443 + become: true + +- name: Link Nessus Agent to Tenable.sc + link_agent_linux: + linking_key: "your_linking_key_here" + name: "my_nessus_agent" + groups: "Linux Servers,Datacenter 2" + network: "Your Network" + host: "tenable.sc.yourdomain.com" + port: 8834 +""" + +RETURN = r""" +msg: + description: The output message of the linking command. + type: str + returned: always + sample: "Agent linked successfully." +rc: + description: The return code of the command. + type: int + returned: always + sample: 0 +changed: + description: A boolean that indicates if there was a change in the state of the target. + returned: always + type: bool + sample: false +""" + + +import subprocess + +from ansible.module_utils.basic import AnsibleModule + + +def link_nessus_agent(module): + linking_key = module.params["linking_key"] + name = module.params["name"] + groups = module.params["groups"] + network = module.params["network"] + host = module.params["host"] + port = module.params["port"] + become = module.params["become"] + + sudo_prefix = "sudo " if become else "" + command = ( + f"{sudo_prefix}/opt/nessus_agent/sbin/nessuscli agent link " + f'--name="{name}" --key={linking_key} --groups="{groups}" ' + f'--network="{network}" --host="{host}" --port={port}' + ) + + try: + result = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT, timeout=10) + output = result.decode("utf-8") + return True, output, 0, True + except subprocess.TimeoutExpired: + module.fail_json(msg="Error: Command timed out. Tenable might not be installed.", rc=124) + except subprocess.CalledProcessError as e: + error_message = e.output.decode() + module.fail_json(msg=f"Failed to link Nessus Agent: {error_message}", rc=e.returncode) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + linking_key=dict(required=True, type="str", no_log=True), + name=dict(required=True, type="str"), + groups=dict(required=True, type="str"), + network=dict(required=True, type="str"), + host=dict(required=True, type="str"), + port=dict(required=True, type="int"), + become=dict(required=False, type="bool", default=False), + ), + supports_check_mode=False, + ) + + is_linked, output, rc, changed = link_nessus_agent(module) + if is_linked: + module.exit_json(changed=changed, msg=output, rc=rc) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/link_agent_windows.ps1 b/plugins/modules/link_agent_windows.ps1 new file mode 100644 index 0000000..fc1465d --- /dev/null +++ b/plugins/modules/link_agent_windows.ps1 @@ -0,0 +1,70 @@ +#!powershell +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic + +#Requires -Module Ansible.ModuleUtils.Legacy + +$spec = @{ + options = @{ + linking_key = @{ type = 'str'; required = $true; no_log = $true } + name = @{ type = 'str'; required = $true } + groups = @{ type = 'str'; required = $true } + network = @{ type = 'str'; required = $true } + host = @{ type = 'str'; required = $true } + port = @{ type = 'int'; required = $true } + } + supports_check_mode = $false +} + +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + +$linking_key = $module.Params.linking_key +$name = $module.Params.name +$groups = $module.Params.groups +$network = $module.Params.network +$tenable_host = $module.Params.host +$port = $module.Params.port + +$nessuscliPath = 'C:\Program Files\Tenable\Nessus Agent\nessuscli.exe' +$arguments = "agent link --key=`"$linking_key`" --name=`"$name`" --groups=`"$groups`" --network=`"$network`" --host=$tenable_host --port=$port" + +try { + $processInfo = New-Object System.Diagnostics.ProcessStartInfo + $processInfo.FileName = $nessuscliPath + $processInfo.RedirectStandardError = $true + $processInfo.RedirectStandardOutput = $true + $processInfo.UseShellExecute = $false + $processInfo.Arguments = $arguments + $processInfo.CreateNoWindow = $true + + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $processInfo + $process.Start() | Out-Null + $process.WaitForExit() + + $stdout = $process.StandardOutput.ReadToEnd() + $stderr = $process.StandardError.ReadToEnd() + + $maskedArguments = $arguments -replace "--key=`"$linking_key`"", "--key=******" + + $module.Result['command_output'] = $stdout + $module.Result['command_error'] = $stderr + $module.Result['command'] = $maskedArguments + + if ($stdout -like "*Link fail*") { + $module.Result['changed'] = $false + $module.Result['failed'] = $true + $module.Result['msg'] = "An error occurred executing the command: $stderr" + } + else { + $module.Result['changed'] = $true + $module.Result['msg'] = $stdout + } +} +catch { + $module.FailJson("Error linking the agent to Tenable.IO: $_") +} + +$module.ExitJson() diff --git a/plugins/modules/link_agent_windows.py b/plugins/modules/link_agent_windows.py new file mode 100644 index 0000000..dbc637d --- /dev/null +++ b/plugins/modules/link_agent_windows.py @@ -0,0 +1,100 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: link_agent_windows +short_description: Links a Tenable.IO agent to a windows. +version_added: "0.0.1" +description: + - Links a Tenable.IO agent to a windows server using provided agent linking key and configuration. + - This module utilizes PowerShell to execute the Tenable Nessus Agent 'nessuscli.exe' command to link an agent. + - Requires PowerShell v5.1 or higher on Windows Server 2016 and higher. +options: + linking_key: + description: + - The agent linking key as provided by Tenable.IO. + required: true + type: str + name: + description: + - The name for the agent being linked. + - It is normally use the hostname of the machine. + required: true + type: str + groups: + description: + - The groups to which the agent should belong. + - This is a previous agent groups that existis + - Check list_agent_groups module for more. + required: true + type: str + network: + description: + - The network where the agent will be connected. + - This a network that needs to exists. + - Check list_networks module for more. + required: true + type: str + host: + description: + - The domain of tenable. + required: true + type: str + port: + description: + - The port of Tenable. + required: true + type: int +notes: + - This module does not support check mode. + - Nesuscli.exe path of the module is the default C:\Program Files\Tenable\Nessus Agent\nessuscli.exe + - Ensure that the 'nessuscli.exe' path is correct and accessible on the system where this module is run. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +""" + +EXAMPLES = r""" +- name: Link Nessus Agent to Tenable.io + link_agent_windows: + linking_key: "{{ your_linking_key_here }}" + name: "{{ ansible_hostname }}" + groups: "{{ tenable_group }}" + network: "{{ tenable_network }}" + host: "cloud.tenable.com" + port: 443 + +- name: Link Nessus Agent to Tenable.sc + link_agent_windows: + linking_key: "your_linking_key_here" + name: "my_nessus_agent" + groups: "Linux Servers,Datacenter 2" + network: "Your Network" + host: "tenable.yourdomain.com" + port: 8834 + become: true +""" + +RETURN = r""" +msg: + description: The output message of the linking command. + type: str + returned: always + sample: "Agent linked successfully." +rc: + description: The return code of the command. + type: int + returned: always + sample: 0 +changed: + description: A boolean that indicates if there was a change in the state of the target. + returned: always + type: bool + sample: false +""" diff --git a/plugins/modules/list_agent_exclusion.py b/plugins/modules/list_agent_exclusion.py new file mode 100644 index 0000000..c068f18 --- /dev/null +++ b/plugins/modules/list_agent_exclusion.py @@ -0,0 +1,120 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_agent_exclusion +short_description: Returns the list of current agent exclusions. +version_added: "0.0.1" +description: + - This module returns the list of current agent exclusions. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/agent-exclusions-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List agent exclusions + list_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List agent exclusions with enviroment creds + list_agent_exclusion: +""" + +RETURN = r""" +exclusions: + description: List of scan exclusions. + returned: success + type: list + elements: dict + contains: + schedule: + description: Schedule details for the exclusion. + type: dict + contains: + endtime: + description: The end time of the schedule. + type: str + sample: "2019-12-31 19:35:00" + enabled: + description: Indicates if the schedule is enabled. + type: bool + sample: true + rrules: + description: Recurrence rules for the schedule. + type: dict + contains: + freq: + description: Frequency of the recurrence. + type: str + sample: "DAILY" + interval: + description: Interval for the recurrence. + type: int + sample: 8 + byweekday: + description: Days of the week for the recurrence. + type: str + sample: "SU,MO" + bymonthday: + description: Days of the month for the recurrence. + type: int + sample: 9 + timezone: + description: Timezone for the schedule. + type: str + sample: "US/Pacific" + starttime: + description: The start time of the schedule. + type: str + sample: "2018-12-31 19:35:00" + last_modification_date: + description: The date when the exclusion was last modified. + type: int + sample: 1543541807 + creation_date: + description: The date when the exclusion was created. + type: int + sample: 1543541807 + description: + description: Description of the exclusion. + type: str + sample: "Router scan exclusion" + name: + description: Name of the exclusion. + type: str + sample: "Routers" + id: + description: ID of the exclusion. + type: int + sample: 124234 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "scanners/null/agents/exclusions" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_agent_filters.py b/plugins/modules/list_agent_filters.py new file mode 100644 index 0000000..0154898 --- /dev/null +++ b/plugins/modules/list_agent_filters.py @@ -0,0 +1,111 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_agent_filters +short_description: List filters for agents +version_added: "0.0.1" +description: + - This module list filters for agents + - Lists the filtering, sorting, and pagination capabilities available for agent records on endpoints that support them + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-filters-agents-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List agent filters + list_agent_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List agent filters with enviroment creds + list_agent_filters: +""" + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "filters/scans/agents" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_agent_groups.py b/plugins/modules/list_agent_groups.py new file mode 100644 index 0000000..a1bb84c --- /dev/null +++ b/plugins/modules/list_agent_groups.py @@ -0,0 +1,142 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_agent_groups +short_description: Retrieves a list of agent groups. +version_added: "0.0.1" +description: + - This module retrieves a list of agent groups. + - The module is made from https://developer.tenable.com/reference/agents-agent-info docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List agent groups + list_agent_groups: + access_key: "your_access_key" + secret_key: "your_secret_key" + +- name: List agent groups with envioment creds + list_agent_groups: +""" + +RETURN = r""" +api_response: + description: The API response containing a list of assets. + returned: on success + type: dict + contains: + data: + description: A list of agent groups. + type: dict + contains: + assets: + description: Details of each group. + type: list + elements: dict + contains: + groups: + description: Details information of the agent group. + type: list + elements: dict + contains: + id: + description: Unique identifier for the group. + type: int + returned: always + sample: 12345 + uuid: + description: UUID associated with the group. + type: str + returned: always + sample: "12345678" + name: + description: Name of the group. + type: str + returned: always + sample: "account_1" + creation_date: + description: Timestamp when the group was created. + type: int + returned: always + sample: 111111 + last_modification_date: + description: Timestamp when the group was last modified. + type: int + returned: always + sample: 222222 + timestamp: + description: Timestamp when the last action was performed on the group. + type: int + returned: always + sample: 333333 + shared: + description: Indicates if the group is shared with other users. + type: int + returned: always + sample: 1 + owner: + description: The system account that owns the group. + type: str + returned: always + sample: "system" + owner_id: + description: The system-generated ID of the owner. + type: int + returned: always + sample: 1111 + owner_name: + description: The name of the owner. + type: str + returned: always + sample: "system" + owner_uuid: + description: The UUID of the owner. + type: str + returned: always + sample: "11111" + user_permissions: + description: The permissions associated with the user. + type: int + returned: always + sample: 111 + agents_count: + description: Number of agents in the group. + type: int + returned: always + sample: 0 + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + run_module(module, "scanners/null/agent-groups", method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_agents.py b/plugins/modules/list_agents.py new file mode 100644 index 0000000..701b060 --- /dev/null +++ b/plugins/modules/list_agents.py @@ -0,0 +1,287 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_agents +short_description: Returns a list of agents for the specified scanner. +version_added: "0.0.1" +description: + - This module returns a list of agents for the specified scanner. + - Supporting complex filtering,wilcarding, sorting, limit. + - Module made from https://developer.tenable.com/reference/agents-list docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.generics + - valkiriaaquatica.tenable.filters_wildcards + - valkiriaaquatica.tenable.filter_type +""" + +EXAMPLES = r""" +- name: List agents that + list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: platform + operator: eq + value: LINUX + +- name: List agents that contain test in its name using enviroment cred + list_agents: + wildcard_text: test + wildcard_fields: name + +- name: List all agents using enviroment credentials + list_agents: + + +- name: List all agents + list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List all agents that in their ip and name contain 192.168. + list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + wildcard_text: 192.168. + wildcard_fields: ip,name + +- name: List agents with specified filters and wildcard using env variables + list_agents: + wildcard_text: POLLER + wildcard_fields: name + filters: + - type: core_version + operator: match + value: 10.4.4 + - type: platform + operator: eq + value: LINUX + - type: groups + operator: eq + value: 127110 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + returned: on success + type: dict + contains: + data: + description: Contains detailed information about agents and pagination settings. + type: dict + contains: + agents: + description: A list of agent details retrieved from the API. + type: list + elements: dict + contains: + aws_account_id: + description: AWS account identifier associated with the agent. + type: str + returned: when available + sample: "12334" + aws_instance_id: + description: AWS instance identifier for the agent. + type: str + returned: when available + sample: "i-5445545d452" + core_build: + description: Core build version of the agent. + type: str + returned: when available + sample: "10" + core_version: + description: Core version of the agent. + type: str + returned: when available + sample: "10.6.2" + distro: + description: Distribution information of the agent's operating system. + type: str + returned: when available + sample: "es7-x86-64" + groups: + description: List of groups the agent belongs to. + type: list + elements: dict + contains: + id: + description: Unique identifier for the group. + type: int + returned: always + sample: 123456 + name: + description: Name of the group. + type: str + returned: always + sample: "name" + id: + description: Unique identifier of the agent. + type: int + returned: always + sample: 12652464 + ip: + description: IP address of the agent. + type: str + returned: always + sample: "192.168.1.120" + last_connect: + description: Timestamp of the last time the agent connected. + type: int + returned: when available + sample: 1713870756 + last_scanned: + description: Timestamp of the last scan performed by the agent. + type: int + returned: when available + sample: 1713769041 + linked_on: + description: Timestamp when the agent was linked to the network. + type: int + returned: when available + sample: 1708585081 + name: + description: Name of the agent. + type: str + returned: always + sample: "poller" + network_name: + description: Name of the network the agent is associated with. + type: str + returned: when available + sample: "AWS_ADA" + network_uuid: + description: UUID of the network the agent is associated with. + type: str + returned: when available + sample: "12345" + platform: + description: Platform of the agent. + type: str + returned: always + sample: "LINUX" + plugin_feed_id: + description: Plugin feed ID associated with the agent. + type: str + returned: when available + sample: "202404230143" + status: + description: Current status of the agent. + type: str + returned: always + sample: "on" + supports_remote_logs: + description: Indicates if the agent supports remote logs. + type: bool + returned: always + sample: true + supports_remote_settings: + description: Indicates if the agent supports remote settings. + type: bool + returned: always + sample: true + uuid: + description: UUID of the agent. + type: str + returned: always + sample: "545621-545g-51564fr-5645" + pagination: + description: Pagination details of the response. + type: dict + contains: + limit: + description: The maximum number of records returned in the response. + type: int + returned: always + sample: 1 + offset: + description: The starting point from which records are returned. + type: int + returned: always + sample: 0 + sort: + description: List of sort conditions applied to the response. + type: list + elements: dict + contains: + name: + description: Name of the field by which the results are sorted. + type: str + returned: always + sample: "name" + order: + description: Order of sorting, ascending or descending. + type: str + returned: always + sample: "asc" + total: + description: Total number of items available. + type: int + returned: always + sample: 163 + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_special_filter +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec( + "access_key", + "secret_key", + "filters", + "filter_type", + "wildcard_text", + "wildcard_fields", + "limit", + "offset", + "sort", + ) + module = AnsibleModule(argument_spec=common_spec, supports_check_mode=False) + + endpoint = "scanners/null/agents" + + def query_params(): + return add_custom_filters( + build_query_parameters( + filter_type=module.params["filter_type"], + wildcard_text=module.params["wildcard_text"], + wildcard_fields=module.params["wildcard_fields"], + limit=module.params["limit"], + offset=module.params["offset"], + sort=module.params["sort"], + ), + module.params["filters"], + handle_special_filter, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_agents_by_group.py b/plugins/modules/list_agents_by_group.py new file mode 100644 index 0000000..a216d86 --- /dev/null +++ b/plugins/modules/list_agents_by_group.py @@ -0,0 +1,281 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_agents_by_group +short_description: Returns a list of agents for the specified agent group. +version_added: "0.0.1" +description: + - This module retrieves a list of agents from Tenable.io + - Supporting complex filtering,wilcarding, sorting, limit. + - Module made from https://developer.tenable.com/reference/agent-group-list-agents docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + agent_group_id: + description: + - The id of the agent group. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.generics + - valkiriaaquatica.tenable.filters_wildcards + - valkiriaaquatica.tenable.filter_type +""" + +EXAMPLES = r""" +- name: List agents by group wiht enviroment creentials + list_agents_by_group: + agent_group_id: 123456 + filters: + - type: core_version + operator: match + value: "10.6.2" + +- name: List agents by group with variables + list_agents_by_group: + access_key: "your_access_key" + secret_key: "your_secret_key" + agent_group_id: 123456 + +- name: List agents by group with variables + list_agents_by_group: + access_key: "your_access_key" + secret_key: "your_secret_key" + agent_group_id: 123456 + wildcard_text: bastion +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + returned: on success + type: dict + contains: + data: + description: Contains detailed information about the agents and pagination. + type: dict + contains: + agents: + description: A list of agents that are in the group. + type: list + elements: dict + contains: + aws_account_id: + description: AWS account identifier associated with the agent. + type: str + returned: when available + sample: "12345" + aws_instance_id: + description: AWS instance identifier for the agent. + type: str + returned: when available + sample: "i-12345" + aws_public_hostname: + description: Public AWS hostname associated with the agent. + type: str + returned: when available + sample: "HOSTNAME" + aws_public_ipv4: + description: Public IPv4 address of the agent in AWS. + type: str + returned: when available + sample: "1.1.1.1" + core_build: + description: Core build version of the agent. + type: str + returned: when available + sample: "1" + core_version: + description: Core version of the agent. + type: str + returned: when available + sample: "1.1.1" + distro: + description: Distribution information of the agent operating system. + type: str + returned: when available + sample: "win-x86-64" + groups: + description: List of groups the agent belongs to. + type: list + elements: dict + contains: + id: + description: Unique identifier for the group. + type: int + returned: always + sample: 1111 + name: + description: Name of the group. + type: str + returned: always + sample: "group1" + id: + description: Unique identifier of the agent. + type: int + returned: always + sample: 123466 + ip: + description: IP address of the agent. + type: str + returned: always + sample: "172.31.34.199" + last_connect: + description: Timestamp of the last time the agent connected to the network. + type: int + returned: when available + sample: 1111 + last_scanned: + description: Timestamp of the last scan performed by the agent. + type: int + returned: when available + sample: 1111 + linked_on: + description: Timestamp when the agent was linked to the network. + type: int + returned: when available + sample: 1111 + name: + description: Name of the agent. + type: str + returned: always + sample: "name" + network_uuid: + description: Network UUID associated with the agent. + type: str + returned: when available + sample: "1111" + platform: + description: Platform of the agent. + type: str + returned: always + sample: "WINDOWS" + plugin_feed_id: + description: Plugin feed ID associated with the agent. + type: str + returned: when available + sample: "1111" + status: + description: Current status of the agent. + type: str + returned: always + sample: "off" + supports_remote_logs: + description: Indicates if the agent supports remote logs. + type: bool + returned: always + sample: false + supports_remote_settings: + description: Indicates if the agent supports remote settings. + type: bool + returned: always + sample: true + uuid: + description: UUID of the agent. + type: str + returned: always + sample: "1111" + pagination: + description: Pagination details of the response. + type: dict + contains: + limit: + description: The maximum number of records returned in the response. + type: int + returned: always + sample: 1 + offset: + description: The starting point from which records are returned. + type: int + returned: always + sample: 0 + sort: + description: List of sort conditions applied to the response. + type: list + elements: dict + contains: + name: + description: Name of the field by which the results are sorted. + type: str + returned: always + sample: "name" + order: + description: Order of sorting, ascending or descending. + type: str + returned: always + sample: "asc" + total: + description: Total number of items available. + type: int + returned: always + sample: 163 + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 + +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_special_filter +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec( + "access_key", + "secret_key", + "filters", + "filter_type", + "wildcard_text", + "wildcard_fields", + "limit", + "offset", + "sort", + ) + special_args = { + "agent_group_id": { + "required": True, + "type": "str", + }, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agent-groups/{module.params['agent_group_id']}/agents" + + def query_params(): + return add_custom_filters( + build_query_parameters( + filter_type=module.params["filter_type"], + wildcard_text=module.params["wildcard_text"], + wildcard_fields=module.params["wildcard_fields"], + limit=module.params["limit"], + offset=module.params["offset"], + sort=module.params["sort"], + ), + module.params["filters"], + handle_special_filter, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_asset_filters.py b/plugins/modules/list_asset_filters.py new file mode 100644 index 0000000..618dd5c --- /dev/null +++ b/plugins/modules/list_asset_filters.py @@ -0,0 +1,111 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_asset_filters +short_description: List filters for assets +version_added: "0.0.1" +description: + - This module list possible filters for assets + - Lists the filtering, sorting, and pagination capabilities available for asset records on endpoints that support them + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-filters-assets-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List tag asset filters + list_asset_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List tag asset filters with enviroment creds + list_asset_filters: +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "filters/workbenches/assets" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_asset_filters_vtwo.py b/plugins/modules/list_asset_filters_vtwo.py new file mode 100644 index 0000000..607c3c5 --- /dev/null +++ b/plugins/modules/list_asset_filters_vtwo.py @@ -0,0 +1,131 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_asset_filters_vtwo +short_description: List filters for assets returning also the tag. +version_added: "0.0.1" +description: + - This module list filters for assets returning also the tag. + - For more information about these filters, see https://developer.tenable.com/docs/workbench-filters + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-filters-assets-list-v2 docs. +options: + tag_uuids: + description: + - A list of tag UUIDs that are guaranteed to be returned in the initial data set of the tag_uuid filter, if the tags exist. + required: true + type: list + elements: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List asset filters with tag in response + list_asset_filters_vtwo: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + tag_uuids: + - 123456 + +- name: List tag asset filters with tag in response with enviroment creds + list_asset_filters_vtwo: + tag_uuids: + - 123456 + - 987654 +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "tag_uuids": {"required": True, "type": "list", "elements": "str"} + } # unique for this module and required + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "filters/workbenches/assets" + + payload_keys = ["tag_uuids"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_asset_tag_filters.py b/plugins/modules/list_asset_tag_filters.py new file mode 100644 index 0000000..a09ea46 --- /dev/null +++ b/plugins/modules/list_asset_tag_filters.py @@ -0,0 +1,114 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_asset_tag_filters +short_description: Returns a list of asset tag filters +version_added: "0.0.1" +description: + - This module retrieves a list of all available time zones from Tenable. + - Returns a list of filters that you can use to create the rules for applying dynamic tags. + - ncludes the field or tag names to match, the operators that you can use with the filter. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/tags-list-asset-filters docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List tag asset filters + list_asset_tag_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List tag asset filters with enviroment creds + list_asset_tag_filters: +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "tags/assets/filters" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_asset_vulnerabilities.py b/plugins/modules/list_asset_vulnerabilities.py new file mode 100644 index 0000000..736de50 --- /dev/null +++ b/plugins/modules/list_asset_vulnerabilities.py @@ -0,0 +1,172 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_asset_vulnerabilities +short_description: Lists vulnerabilities for a specific asset in Tenable.io. +version_added: "0.0.1" +description: + - Retrieves a list of recorded vulnerabilities for a specified asset. + - Allows filtering results based on various criteria such as date range and severity. + - Module made from https://developer.tenable.com/reference/workbenches-asset-vulnerabilities + - The response to this endpoint is not returning any text, even if the asset does not exist.s +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset + - valkiriaaquatica.tenable.date + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.filter_search_type + - valkiriaaquatica.tenable.date +""" + +EXAMPLES = r""" +- name: Get all vulnerbailties from an asset + list_asset_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "123456789" + +- name: Get all vulnerbailties from an asset within a date_range + list_asset_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "123456789" + date_range: 80 + +- name: Get vulnerbailties filtered by three conditionals and condition + list_asset_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "123456789" + filter_search_type: "and" + filters: + - type: severity + operator: eq + value: Info + - type: plugin.family_id + operator: eq + value: 23 + - type: tracking.state + operator: eq + value: Active + +- name: Get vulnerbailties filtered by three conditionals and or condition + list_asset_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "123456789" + filter_search_type: "or" + filters: + - type: severity + operator: eq + value: Info + - type: plugin.family_id + operator: eq + value: 23 + - type: tracking.state + operator: eq + value: Active + +- name: Get vulns from asset applying filters and enviroment creds + list_asset_vulnerabilities: + asset_uuid: "123456789" + filters: + - type: plugin.attributes.solution + operator: match + value: "Update the affected packages" + - type: plugin.attributes.cvss_base_score + operator: gte + value: "9.8" + - type: tracking.state + operator: eq + value: "Active" +""" + + +RETURN = r""" +api_response: + description: The API response containing vulnerability details for the specified asset. + returned: on success + type: dict + contains: + data: + description: The data containing the details of the assets and their vulnerabilities. + type: dict + contains: + total_asset_count: + description: The total number of assets matching the filter criteria. + type: int + returned: always + sample: 0 + total_vulnerability_count: + description: The total number of vulnerabilities found for the assets. + type: int + returned: always + sample: 17 + vulnerabilities: + description: A list of vulnerabilities associated with the assets. + type: list + elements: dict + returned: always + sample: [ + { + "accepted_count": 0, + "count": 1, + "counts_by_severity": [ + { + "count": 1, + "value": 4 + } + ], + "cvss3_base_score": 9.8, + "cvss_base_score": 10.0, + "plugin_family": "Red Hat Local Security Checks", + "plugin_id": 100400, + "plugin_name": "RHEL 6 / 7 : samba (RHSA-2017:1270) (SambaCry)", + "recasted_count": 0, + "severity": 4, + "vpr_score": 7.4, + "vulnerability_state": "Resurfaced" + } + ] +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid", "filters", "filter_search_type", "date_range") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"workbenches/assets/{module.params['asset_uuid']}/vulnerabilities" + + def query_params(): + return add_custom_filters( + build_query_parameters( + date_range=module.params["date_range"], filter_search_type=module.params["filter_search_type"] + ), + module.params["filters"], # this is handled by handle_special_filter + handle_multiple_filters, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_asset_vulnerabilities_for_plugin.py b/plugins/modules/list_asset_vulnerabilities_for_plugin.py new file mode 100644 index 0000000..194db83 --- /dev/null +++ b/plugins/modules/list_asset_vulnerabilities_for_plugin.py @@ -0,0 +1,210 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_asset_vulnerabilities_for_plugin +short_description: Retrieves the vulnerability outputs for a plugin recorded on a specified asset. +version_added: "0.0.1" +description: + - This module retrieves the vulnerability outputs for a plugin recorded on a specified asset. + - Multiple filters can be applied and get full of default info rmo assets. + - Note This endpoint is not intended for large or frequent exports of vulnerability or assets data + - For information and best practices for retrieving vulnerability see https://developer.tenable.com/docs/retrieve-vulnerability-data-from-tenableio + - For information and best practices for retrieving assets see https://developer.tenable.com/docs/retrieve-asset-data-from-tenableio + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - This module was made from https://developer.tenable.com/reference/workbenches-asset-vulnerability-output docs. +options: + asset_id: + description: + - The UUID of the asset for which vulnerability details are to be retrieved. + type: str + required: true + plugin_id: + description: + - The ID of the plugin associated with the vulnerability data. + type: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.date + - valkiriaaquatica.tenable.filter_search_type +""" + + +EXAMPLES = r""" +- name: Get details of the plugin + list_asset_vulnerabilities_for_plugin: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "123456" + plugin_id: "987654" + +- name: Get details of the plugin using filters and enviroment creds in last five days + list_asset_vulnerabilities_for_plugin: + asset_id: "123456" + plugin_id: "987654" + date_range: 5 + filters: + - type: plugin.name + operator: match + value: RHEL +""" + +RETURN = r""" +api_response: + description: The API response containing a list of assets. + returned: on success + type: dict + contains: + data: + description: A list of assets retrieved from the API. + type: dict + contains: + outputs: + description: Details of each asset including specific outputs from plugins. + type: list + elements: dict + contains: + plugin_output: + description: Description of the output provided by the plugin for a specific asset. + type: str + returned: always + sample: "Port 1111/udp was found to be open" + states: + description: List of states related to the asset. + type: list + elements: dict + contains: + name: + description: The state name indicating the current state of the asset. + type: str + returned: always + sample: "active" + results: + description: Results associated with the state of the asset. + type: list + elements: dict + contains: + application_protocol: + description: The application protocol used by the asset, if any. + type: str + returned: when available + sample: null + assets: + description: List of assets associated with the current state. + type: list + elements: dict + contains: + first_seen: + description: The first time the asset was seen. + type: str + returned: always + sample: "date" + fqdn: + description: Fully Qualified Domain Name of the asset. + type: str + returned: always + sample: "fqdn" + hostname: + description: Hostname of the asset. + type: str + returned: always + sample: "hostname" + id: + description: Unique identifier of the asset. + type: int + returned: always + sample: "1123456" + ipv4: + description: IPv4 addresses associated with the asset. + type: str + returned: always + sample: "10.10.10.1,192.168.1.10" + ipv6: + description: IPv6 addresses associated with the asset, if any. + type: str + returned: when available + sample: "" + last_seen: + description: The last time the asset was seen. + type: str + returned: always + sample: "date" + netbios_name: + description: NetBIOS name of the asset, if available. + type: str + returned: when available + sample: null + uuid: + description: UUID of the asset. + type: str + returned: always + sample: "123456789" + port: + description: Port number associated with the asset. + type: int + returned: always + sample: 1194 + severity: + description: Severity level of the issue found on the asset. + type: int + returned: always + sample: 0 + transport_protocol: + description: Transport protocol used by the asset. + type: str + returned: always + sample: "udp" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "date_range", "filters", "filter_search_type") + specific_spec = { + "asset_id": {"required": True, "type": "str"}, + "plugin_id": {"required": True, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"workbenches/assets/{module.params['asset_id']}/vulnerabilities/{module.params['plugin_id']}/outputs" + + def query_params(): + return add_custom_filters( + build_query_parameters( + date_range=module.params["date_range"], filter_search_type=module.params["filter_search_type"] + ), + module.params["filters"], + handle_multiple_filters, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_asset_with_vulnerabilities.py b/plugins/modules/list_asset_with_vulnerabilities.py new file mode 100644 index 0000000..f3733dc --- /dev/null +++ b/plugins/modules/list_asset_with_vulnerabilities.py @@ -0,0 +1,160 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_asset_with_vulnerabilities +short_description: Returns a list of assets with their vulnerability data from Tenable.io. +version_added: "0.0.1" +description: + - This modules returns a list of assets and their vulnerabilities associated. + - Filters can be applied. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/workbenches-assets-vulnerabilities docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.date + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.filter_search_type +""" + +EXAMPLES = r""" +- name: Get assets within vulnerbailities have 1 day old + list_asset_with_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + date_range: 1 + +- name: Retrieve assets with a specific IPv4 address + list_asset_with_vulnerabilities: + access_key: your_access_key + secret_key: your_secret_key + filters: + - type: ipv4 + operator: eq + value: "192.168.1.150" + +- name: Get assets with log4j and with enviroment credential variables + list_asset_with_vulnerabilities: + filter_search_type: and + filters: + - type: plugin.name + operator: match + value: log4j + +- name: Get assets from a project that have critical vulns in the last week using enviroment creds + list_asset_with_vulnerabilities: + date_range: 7 + filter_search_type: and + filters: + - type: severity + operator: eq + value: "Critical" + - type: tag.Project + operator: set-has + value: "Project Name" + +- name: Get assets where crictical vulns where found in last 24 hours + list_asset_with_vulnerabilities: + date_range: 1 + filters: + - type: severity + operator: eq + value: "Critical" + +- name: Get assets filtering with ip + list_asset_with_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: host.target + operator: eq + value: "172.31.1.4,192.168.32.118" + +- name: Get assets that have a vulnerability that the solution is to Update the affected packages and enviroment creds + list_asset_with_vulnerabilities: + filters: + - type: plugin.attributes.solution + operator: match + value: "Update the affected packages" + tags: + - vulns +""" + +RETURN = r""" +api_response: + description: The API response containing assets details. + returned: on success + type: complex + contains: + data: + description: The data containing the list of assets and their details. + type: dict + contains: + assets: + description: A list of assets that match the filter criteria. In this case just 1 + type: list + elements: dict + sample: [ + { + "agent_name": ["name_of_asset"], + "fqdn": ["name_of_asset_fdqn"], + "id": "022342281", + "ipv4": ["192.168.1.120", "172.145..6"], + "ipv6": ["451sd::5615d:14ff:541457:451", "451sd::5615d:14ff:541457:452"], + "last_seen": "2021-04-22T06:53:37.428Z", + "netbios_name": [], + "severities": [ + {"count": 0, "level": 0, "name": "Info"}, + {"count": 0, "level": 1, "name": "Low"}, + {"count": 2, "level": 2, "name": "Medium"}, + {"count": 0, "level": 3, "name": "High"}, + {"count": 1, "level": 4, "name": "Critical"} + ], + "total": 3 + } + ] + total_asset_count: + description: The total number of assets matching the filter criteria. + type: int + sample: 1 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "date_range", "filters", "filter_search_type") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "workbenches/assets/vulnerabilities" + + def query_params(): + return add_custom_filters( + build_query_parameters( + date_range=module.params["date_range"], filter_search_type=module.params["filter_search_type"] + ), + module.params["filters"], + handle_multiple_filters, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_assets.py b/plugins/modules/list_assets.py new file mode 100644 index 0000000..f46d1c6 --- /dev/null +++ b/plugins/modules/list_assets.py @@ -0,0 +1,288 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_assets +short_description: Retrieve a filtered list of assets from Tenable.io +version_added: "0.0.1" +description: + - This module retrieves a filtered list of assets from Tenable.io. + - Multiple filters can be applied and get full of default info rmo assets. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - This module was made from https://developer.tenable.com/reference/workbenches-assets docs. +options: + all_fields: + description: + - Specifies whether to include all fields ('full') or only the default fields ('default') in the returned data. + type: str + required: false + choices: ['full', 'default'] +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.date + - valkiriaaquatica.tenable.filter_search_type +""" + +EXAMPLES = r""" +- name: Get Assets based on thei size + list_assets: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: "aws_ec2_instance_type" + operator: "eq" + value: "t2.micro" + +- name: Get Aall assets from all networks except one + list_assets: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: network_id + operator: neq + value: 123456 + +- name: Get all assets from two networks + list_assets: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filter_search_type: or + filters: + - type: network_id + operator: eq + value: 123456 + - type: network_id + operator: eq + value: 5678 + +- name: Get an asset from aws instance id + list_assets: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: aws_ec2_instance_id + operator: eq + value: i-123456789 + +- name: List assets from a network_id using eniroment creds + list_assets: + filters: + - type: network_id + operator: eq + value: 123456 + +- name: List assets from a network_id using eniroment creds and getting full data + list_assets: + filters: + - type: network_id + operator: eq + value: 123456 + - type: fqdn + operator: eq + value: "i123451795" + all_fields: full +""" + +RETURN = r""" +api_response: + description: The API response containing a list of assets. + type: dict + returned: on success + contains: + data: + description: A list of assets retrieved from the API. + type: dict + returned: always + contains: + assets: + description: Details of each asset. + type: list + elements: dict + returned: always + contains: + acr_drivers: + description: Indicates the ACR drivers associated with the asset, if applicable. + type: str + returned: always + sample: null + acr_score: + description: The ACR score of the asset, if applicable. + type: int + returned: always + sample: null + agent_name: + description: List of agent names associated with the asset. + type: list + elements: str + returned: always + sample: [] + aws_ec2_name: + description: List of AWS EC2 names associated with the asset, if applicable. + type: list + elements: str + returned: always + sample: ["aws_iam_name"] + exposure_confidence_value: + description: The confidence value of the exposure assessment, if applicable. + type: str + returned: always + sample: null + exposure_score: + description: The score of the exposure assessment, if applicable. + type: str + returned: always + sample: null + fqdn: + description: List of fully qualified domain names associated with the asset. + type: list + elements: str + returned: always + sample: ["111l"] + has_agent: + description: Indicates whether the asset has an associated agent. + type: bool + returned: always + sample: true + hostname: + description: List of hostnames associated with the asset. + type: list + elements: str + returned: always + sample: ["iam_hostname"] + id: + description: Unique identifier of the asset. + type: str + returned: always + sample: "fe56ee29-0213-11" + ipv4: + description: List of IPv4 addresses associated with the asset. + type: list + elements: str + returned: always + sample: ["222"] + ipv6: + description: List of IPv6 addresses associated with the asset. + type: list + elements: str + returned: always + sample: [] + last_scan_target: + description: The target of the last scan performed on the asset, if applicable. + type: str + returned: always + sample: null + last_seen: + description: The last time the asset was seen by str monitoring systems. + type: str + returned: always + sample: "2023-06-26T16:51:34.609Z" + mac_address: + description: List of MAC addresses associated with the asset. + type: list + elements: str + returned: always + sample: ["mac"] + netbios_name: + description: List of NetBIOS names associated with the asset. + type: list + elements: str + returned: always + sample: ["iam_netbios_name"] + operating_system: + description: List of operating systems running on the asset. + type: list + elements: str + returned: always + sample: ["Red Hat Enterprise Linux 8.6"] + scan_frequency: + description: The frequency at which the asset is scanned, if regularly scheduled. + type: str + returned: always + sample: null + security_protection_level: + description: The level of security protection assigned to the asset, if applicable. + type: str + returned: always + sample: null + security_protections: + description: List of security protections applied to the asset. + type: list + elements: str + returned: always + sample: [] + sources: + description: Sources that have reported data about the asset. + type: list + elements: dict + returned: always + contains: + first_seen: + description: The first time this source reported data about the asset. + type: str + returned: always + sample: "2023-06-15T12:36:18.502Z" + last_seen: + description: The last time this source reported data about the asset. + type: str + returned: always + sample: "2023-06-26T16:51:34.609Z" + name: + description: Name of the source reporting the data. + type: str + returned: always + sample: "AWS" + total: + description: The total number of assets returned. + type: int + returned: always + sample: 1 + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "date_range", "filters", "filter_search_type", "all_fields") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "workbenches/assets" + + def query_params(): + return add_custom_filters( + build_query_parameters( + date_range=module.params["date_range"], + filter_search_type=module.params["filter_search_type"], + all_fields=module.params["all_fields"], + ), + module.params["filters"], + handle_multiple_filters, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_assignable_scanners.py b/plugins/modules/list_assignable_scanners.py new file mode 100644 index 0000000..8e4a49f --- /dev/null +++ b/plugins/modules/list_assignable_scanners.py @@ -0,0 +1,196 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_assignable_scanners +short_description: Lists all scanners and scanner groups not yet assigned to a custom network object. +version_added: "0.0.1" +description: + - This module lists all scanners and scanner groups not yet assigned to a custom network object. + - The module is made from https://developer.tenable.com/reference/networks-list-assignable-scannersdocs. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. +options: + network_id: + description: + - The UUID of the default network object. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List assignable scanners from a network using enviroment creds + list_assignable_scanners: + network_id: "012345" + +- name: List assignable scanners from a network using enviroment creds + list_assignable_scanners: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "012345" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + returned: always + type: dict + contains: + data: + description: Contains the actual data of the response. + type: dict + contains: + scanners: + description: A list of scanner details retrieved from the API. + type: list + elements: dict + contains: + creation_date: + description: The timestamp when the scanner was created. + type: int + returned: always + sample: 12345 + group: + description: Indicates whether the scanner is part of a group. + type: bool + returned: always + sample: true + id: + description: Unique identifier for the scanner. + type: int + returned: always + sample: 12345 + key: + description: The key associated with the scanner. + type: str + returned: always + sample: "12345" + last_connect: + description: The last connection timestamp of the scanner, null if never connected. + type: int + returned: when available + last_modification_date: + description: The timestamp when the scanner was last modified. + type: int + returned: always + sample: 12345 + linked: + description: Indicates whether the scanner is linked (1) or not (0). + type: int + returned: always + sample: 1 + name: + description: The name of the scanner. + type: str + returned: always + sample: "EU Frankfurt Cloud Scanners" + num_scans: + description: The number of scans conducted by this scanner. + type: int + returned: always + sample: 0 + owner: + description: The system account that owns the scanner. + type: str + returned: always + sample: "system" + owner_id: + description: The system-generated ID of the owner. + type: int + returned: always + sample: 2297093 + owner_name: + description: The name of the owner. + type: str + returned: always + sample: "system" + owner_uuid: + description: The UUID of the owner. + type: str + returned: always + sample: "12345" + pool: + description: Indicates if the scanner is part of a pool. + type: bool + returned: always + sample: true + scan_count: + description: The count of scans assigned to this scanner. + type: int + returned: always + sample: 0 + source: + description: The source of the scanner, e.g., 'service'. + type: str + returned: always + sample: "service" + status: + description: The operational status of the scanner. + type: str + returned: always + sample: "on" + supports_remote_logs: + description: Indicates if the scanner supports remote logs. + type: bool + returned: always + sample: false + supports_remote_settings: + description: Indicates if the scanner supports remote settings adjustments. + type: bool + returned: always + sample: false + timestamp: + description: The timestamp of the last significant update to the scanner's settings. + type: int + returned: always + sample: 12345 + type: + description: The type of scanner, e.g., 'local'. + type: str + returned: always + sample: "local" + uuid: + description: The UUID of the scanner. + type: str + returned: always + sample: "12345" + status_code: + description: HTTP status code returned by the Tenable.io API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + special_args = { + "network_id": {"required": True, "type": "str"}, # it exsits in arguments but is not required and here it is + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"networks/{module.params['network_id']}/assignable-scanners" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_attributes.py b/plugins/modules/list_attributes.py new file mode 100644 index 0000000..f570c24 --- /dev/null +++ b/plugins/modules/list_attributes.py @@ -0,0 +1,68 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_attributes +short_description: Returns a list of custom asset attributes. +version_added: "0.0.1" +description: + - This module returns a list of custom asset attributes. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-v3-asset-attributes-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List all attributes in Tenable + list_attributes: + access_key: "your_access_key" + secret_key: "your_secret_key" +- name: List all attributes in Tenable using envirometn credentials + list_attributes: +""" + +RETURN = r""" +attributes: + description: A list of custom asset attributes. + returned: always + type: list + elements: dict + contains: + id: + description: The unique identifier for the custom asset attribute. + type: str + name: + description: The name of the custom asset attribute. + type: str + description: + description: A description of the custom asset attribute. + type: str +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "api/v3/assets/attributes" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_attributes_assigned_to_asset.py b/plugins/modules/list_attributes_assigned_to_asset.py new file mode 100644 index 0000000..c273f04 --- /dev/null +++ b/plugins/modules/list_attributes_assigned_to_asset.py @@ -0,0 +1,72 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_attributes_assigned_to_asset +short_description: Returns a list of custom asset attributes assigned to the specified asset. +version_added: "0.0.1" +description: + - This module returns a list of custom asset attributes assigned to the specified asset. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-v3-asset-attributes-assigned-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset +""" + +EXAMPLES = r""" +- name: List attributes assigned to asset + list_attributes_assigned_to_asset: + access_key: "your_access_key" + secret_key: "your_secret_key" + asset_uuid: "12345" + +- name: List attributes assigned to asset using enviroment creds + list_attributes_assigned_to_asset: + asset_uuid: "98746" +""" + +RETURN = r""" +attributes: + description: A list of custom asset attributes. + returned: always + type: list + elements: dict + contains: + id: + description: The unique identifier for the custom asset attribute. + type: str + name: + description: The name of the custom asset attribute. + type: str + description: + description: A description of the custom asset attribute. + type: str +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"api/v3/assets/{module.params['asset_uuid']}/attributes" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_aws_scan_targets.py b/plugins/modules/list_aws_scan_targets.py new file mode 100644 index 0000000..432a712 --- /dev/null +++ b/plugins/modules/list_aws_scan_targets.py @@ -0,0 +1,71 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_aws_scan_targets +short_description: Retrieves the key of the requested scanner. +version_added: "0.0.1" +description: + - This module retrieves the key of the requested scanner. + - ists AWS scan targets if the requested scanner is an Amazon Web Services scanner. + - The module is made from https://developer.tenable.com/reference/scanners-get-aws-targets docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: List aws scan targets + list_aws_scan_targets: + access_key: "your_access_key" + secret_key: "your_secret_key" + scanner_id: 11111 + +- name: List aws scan targets using enviroment keys + list_aws_scan_targets: + scanner_id: 11111 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: on success + contains: + object: + description: The targets + type: dict + returned: on success + sample: + object: "aws-target" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scanner_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scanners/{module.params['scanner_id']}/aws-targets" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_credential_filters.py b/plugins/modules/list_credential_filters.py new file mode 100644 index 0000000..748e682 --- /dev/null +++ b/plugins/modules/list_credential_filters.py @@ -0,0 +1,117 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_credential_filters +short_description: List filters for credentials +version_added: "0.0.1" +description: + - This module list filters for credentials + - Lists the filtering, sorting, and pagination capabilities available for credential records on endpoints that support them + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-filters-credentials-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List tag asset filters + list_credential_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List tag asset filters with enviroment creds + list_credential_filters: +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +changed: + description: A boolean that indicates if there was a change in the state of the target. + returned: always + type: bool + sample: false +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "filters/credentials" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_credential_types.py b/plugins/modules/list_credential_types.py new file mode 100644 index 0000000..1996cf2 --- /dev/null +++ b/plugins/modules/list_credential_types.py @@ -0,0 +1,119 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_credential_types +short_description: Lists all credential types supported for managed credentials in Tenable Vulnerability Management. +version_added: "0.0.1" +description: + - This module lists all credential types supported for managed credentials in Tenable Vulnerability Management + - For more information about using the data returned by this endpoint to create managed credentials, + see https://developer.tenable.com/docs/determine-settings-for-credential-type + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/credentials-list-credential-types docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List credential types + list_credential_types: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List credential types file filters with enviroment creds + list_credential_types: +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + credentials: + description: A list of managed credentials. + type: list + elements: dict + contains: + id: + description: The identifier for the credential category. + type: str + sample: "Cloud Services" + category: + description: The category of the credential. + type: str + sample: "Cloud Services" + default_expand: + description: Indicates if the category should be expanded by default. + type: bool + sample: false + types: + description: A list of credential types within the category. + type: list + elements: dict + contains: + id: + description: The identifier for the credential type. + type: str + sample: "Amazon AWS" + name: + description: The name of the credential type. + type: str + sample: "Amazon AWS" + max: + description: The maximum number of this type of credential that can be created. + type: int + sample: 1 + configuration: + description: The configuration settings for the credential type. + type: list + elements: dict + contains: + type: + description: The type of the configuration setting (e.g., text, password). + type: str + sample: "password" + name: + description: The name of the configuration setting. + type: str + sample: "AWS Access Key ID" + required: + description: Indicates if the configuration setting is required. + type: bool + sample: true + id: + description: The identifier for the configuration setting. + type: str + sample: "access_key_id" + expand_settings: + description: Indicates if the settings for the credential type should be expanded by default. + type: bool + sample: true +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "credentials/types" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_exclusions.py b/plugins/modules/list_exclusions.py new file mode 100644 index 0000000..0630787 --- /dev/null +++ b/plugins/modules/list_exclusions.py @@ -0,0 +1,114 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_exclusions +short_description: Lists exclusions for your Tenable Vulnerability Management scans. +version_added: "0.0.1" +description: + - This module lists exclusions for your Tenable Vulnerability Management scans. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/exclusions-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List exclusions in Tenable + list_exclusions: + access_key: "your_access_key" + secret_key: "your_secret_key" +- name: List exclusions in Tenable using envirometn credentials + list_exclusions: +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + exclusions: + description: A list of agent exclusions. + type: list + elements: dict + contains: + schedule: + description: The schedule details of the exclusion. + type: dict + contains: + endtime: + description: The end time of the exclusion formatted as YYYY-MM-DD HH:MM:SS or null. + type: str + sample: null + enabled: + description: If true, the exclusion is scheduled. + type: bool + sample: false + rrules: + description: The recurrence rules for the exclusion or null. + type: dict + sample: null + timezone: + description: The timezone for the exclusion as returned by scans or null. + type: str + sample: null + starttime: + description: The start time of the exclusion formatted as YYYY-MM-DD HH:MM:SS or null. + type: str + sample: null + network_id: + description: The network ID associated with the exclusion. + type: str + sample: "00000000-0000-0000-0000-000000000000" + last_modification_date: + description: The last modification date of the exclusion. + type: int + sample: 1544459404 + creation_date: + description: The creation date of the exclusion. + type: int + sample: 1544459404 + members: + description: The members included in the exclusion. + type: str + sample: "192.0.2.1-192.0.2.255,192.0.2.0/24,host.domain.com" + description: + description: The description of the exclusion or null. + type: str + sample: null + name: + description: The name of the exclusion. + type: str + sample: "Western Region" + id: + description: The identifier of the exclusion. + type: int + sample: 1 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "exclusions" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_folders.py b/plugins/modules/list_folders.py new file mode 100644 index 0000000..c0b83c1 --- /dev/null +++ b/plugins/modules/list_folders.py @@ -0,0 +1,100 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_folders +short_description: Lists both Tenable-provided folders and the current user's custom folders. +version_added: "0.0.1" +description: + - This module lists both Tenable-provided folders and the current user's custom folders. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/folders-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List all folders in Tenable + list_folders: + access_key: "your_access_key" + secret_key: "your_secret_key" +- name: List all folders in Tenable using envirometn credentials + list_folders: +""" + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + type: dict + returned: always + contains: + data: + description: Contains data about folders. + type: dict + contains: + folders: + description: A list of folder details retrieved from the API. + type: list + elements: dict + contains: + custom: + description: Indicates if the folder is custom (1) or not (0). + type: int + returned: always + sample: 0 + default_tag: + description: Indicates if the folder is a default tag (1) or not (0). + type: int + returned: always + sample: 1 + id: + description: Unique identifier for the folder. + type: int + returned: always + sample: 1 + name: + description: Name of the folder. + type: str + returned: always + sample: "name" + type: + description: Type of the folder, e.g., 'main'. + type: str + returned: always + sample: "main" + unread_count: + description: Count of unread items in the folder. + type: int + returned: always + sample: 1 + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + run_module(module, "folders", method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_groups.py b/plugins/modules/list_groups.py new file mode 100644 index 0000000..3a768e3 --- /dev/null +++ b/plugins/modules/list_groups.py @@ -0,0 +1,103 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_groups +short_description: Returns a list of groups in Tenable.IO. +version_added: "0.0.1" +description: + - The module returns a list of groups. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/groups-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List groups using enviroment credentials + list_groups: + register: + - groups + +- name: List groups using keys + list_groups: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: + - groups +""" + +RETURN = r""" +api_response: + description: The API response containing a list of groups. + type: dict + returned: on success + contains: + groups: + description: A list of groups retrieved from the API. + type: list + elements: dict + contains: + uuid: + description: Unique identifier for the group. + type: str + returned: always + sample: "123456" + managed_by_saml: + description: Indicates whether the group is managed by SAML. + type: bool + returned: always + sample: false + name: + description: Name of the group. + type: str + returned: always + sample: "Security Group" + user_count: + description: Number of users in the group. + type: int + returned: always + sample: 24 + id: + description: Numeric ID of the group. + type: int + returned: always + sample: 1234567 + container_uuid: + description: UUID of the container associated with the group. + type: str + returned: always + sample: "123454r" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "groups" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_managed_credentials.py b/plugins/modules/list_managed_credentials.py new file mode 100644 index 0000000..d5daa14 --- /dev/null +++ b/plugins/modules/list_managed_credentials.py @@ -0,0 +1,168 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_managed_credentials +short_description: Lists managed credentials where you have been assigned at least CAN USE (32) permissions. +version_added: "0.0.1" +description: + - This module lists all credential types supported for managed credentials in Tenable Vulnerability Management + - Note This endpoint does not list scan-specific or policy-specific credentials (that is, credentials + stored in either a scan or a policy) + see https://developer.tenable.com/docs/determine-settings-for-credential-type + - To view a list of scan-specific or policy-specific credentials, use the editor details endpoint (GET /editor/{type}/{id}). + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/credentials-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List managed credentials + list_managed_credentials: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List managed credential types file filters with enviroment creds + list_managed_credentials: +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + credentials: + description: A list of managed credentials. + type: list + elements: dict + contains: + uuid: + description: The unique identifier for the credential. + type: str + sample: "a3820211-a59c-4fdd-b6f9-65583f61bf61" + name: + description: The name of the credential. + type: str + sample: "Windows devices (Headquarters)" + description: + description: The description of the credential. + type: str + sample: "Use for scans of Windows devices located at headquarters." + category: + description: The category of the credential. + type: dict + contains: + id: + description: The identifier for the category. + type: str + sample: "Host" + name: + description: The name of the category. + type: str + sample: "Host" + type: + description: The type of the credential. + type: dict + contains: + id: + description: The identifier for the type. + type: str + sample: "Windows" + name: + description: The name of the type. + type: str + sample: "Windows" + created_date: + description: The date the credential was created, in Unix timestamp format. + type: int + sample: 1551295980 + created_by: + description: Information about the user who created the credential. + type: dict + contains: + id: + description: The identifier for the user. + type: int + sample: 15 + display_name: + description: The display name of the user. + type: str + sample: "user@example.com" + last_used_by: + description: Information about the last user who used the credential. + type: dict + contains: + id: + description: The identifier for the user. + type: int + sample: null + display_name: + description: The display name of the user. + type: str + sample: null + permission: + description: The permission level of the credential. + type: int + sample: 32 + user_permissions: + description: The user permissions for the credential. + type: int + sample: 32 + pagination: + description: Information about the pagination of the results. + type: dict + contains: + total: + description: The total number of credentials. + type: int + sample: 1 + limit: + description: The number of credentials per page. + type: int + sample: 50 + offset: + description: The offset for the current page of results. + type: int + sample: 0 + sort: + description: The sorting criteria for the results. + type: list + elements: dict + contains: + name: + description: The field by which the results are sorted. + type: str + sample: "created_date" + order: + description: The order of the sorting (ascending or descending). + type: str + sample: "desc" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "credentials/types" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_network_scanners.py b/plugins/modules/list_network_scanners.py new file mode 100644 index 0000000..265af96 --- /dev/null +++ b/plugins/modules/list_network_scanners.py @@ -0,0 +1,196 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_network_scanners +short_description: Lists all scanners and scanner groups belonging to the specified network object. +version_added: "0.0.1" +description: + - This module lists all scanners and scanner groups belonging to the specified network object. + - The module is made from https://developer.tenable.com/reference/networks-list-scanners . + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. +options: + network_id: + description: + - The UUID of the network object for which you want to list assigned scanners and scanner group. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List network scanners from a network using enviroment creds + list_network_scanners: + network_id: "012345" + +- name: List network scanners from a network using enviroment creds + list_network_scanners: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "012345" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + returned: always + type: dict + contains: + data: + description: Contains the actual data of the response. + type: dict + contains: + scanners: + description: A list of scanner details retrieved from the API. + type: list + elements: dict + contains: + creation_date: + description: The timestamp when the scanner was created. + type: int + returned: always + sample: 12345 + group: + description: Indicates whether the scanner is part of a group. + type: bool + returned: always + sample: true + id: + description: Unique identifier for the scanner. + type: int + returned: always + sample: 12345 + key: + description: The key associated with the scanner. + type: str + returned: always + sample: "12345" + last_connect: + description: The last connection timestamp of the scanner, null if never connected. + type: int + returned: when available + last_modification_date: + description: The timestamp when the scanner was last modified. + type: int + returned: always + sample: 12345 + linked: + description: Indicates whether the scanner is linked (1) or not (0). + type: int + returned: always + sample: 1 + name: + description: The name of the scanner. + type: str + returned: always + sample: "EU Frankfurt Cloud Scanners" + num_scans: + description: The number of scans conducted by this scanner. + type: int + returned: always + sample: 0 + owner: + description: The system account that owns the scanner. + type: str + returned: always + sample: "system" + owner_id: + description: The system-generated ID of the owner. + type: int + returned: always + sample: 2297093 + owner_name: + description: The name of the owner. + type: str + returned: always + sample: "system" + owner_uuid: + description: The UUID of the owner. + type: str + returned: always + sample: "12345" + pool: + description: Indicates if the scanner is part of a pool. + type: bool + returned: always + sample: true + scan_count: + description: The count of scans assigned to this scanner. + type: int + returned: always + sample: 0 + source: + description: The source of the scanner, e.g., 'service'. + type: str + returned: always + sample: "service" + status: + description: The operational status of the scanner. + type: str + returned: always + sample: "on" + supports_remote_logs: + description: Indicates if the scanner supports remote logs. + type: bool + returned: always + sample: false + supports_remote_settings: + description: Indicates if the scanner supports remote settings adjustments. + type: bool + returned: always + sample: false + timestamp: + description: The timestamp of the last significant update to the scanner's settings. + type: int + returned: always + sample: 12345 + type: + description: The type of scanner, e.g., 'local'. + type: str + returned: always + sample: "local" + uuid: + description: The UUID of the scanner. + type: str + returned: always + sample: "12345" + status_code: + description: HTTP status code returned by the Tenable.io API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + special_args = { + "network_id": {"required": True, "type": "str"}, # it exsits in arguments but is not required and here it is + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"networks/{module.params['network_id']}/scanners" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_networks.py b/plugins/modules/list_networks.py new file mode 100644 index 0000000..5cf4acb --- /dev/null +++ b/plugins/modules/list_networks.py @@ -0,0 +1,209 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_networks +short_description: Lists network objects for your organization. +version_added: "0.0.1" +description: + - This module retrieves a lists network objects for your organization. + - Supporting complex filtering, sorting, pagination, and the option to include deleted network objects. + - Module made from https://developer.tenable.com/reference/networks-list docs. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - ft option not included because is only suported OR value, there is no other option. +options: + include_deleted: + description: + - Indicates whether to include deleted network objects in the response. + - Deleted network objects contain the additional attributes, deleted and deleted_by + - Which specifies the date (in Unix time) when the network object was deleted and the UUID of the user that deleted the network object. + required: false + type: bool +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.generics + - valkiriaaquatica.tenable.filters +""" + +EXAMPLES = r""" +- name: List all networks + list_networks: + access_key: your_access_key + secret_key: your_secret_key + register: all_networks + +- name: List all network objects getting keys from enviroment variables + list_networks: + register: all_networks + +- name: List network objects with filters and pagination + list_networks: + access_key: your_access_key + secret_key: your_secret_key + filters: + - type: "name" + operator: "eq" + value: "aws_network_1" + limit: 20 + offset: 0 + sort: "name:asc" + include_deleted: false + +- name: List all tenable networks with names and passing keys through enviroment + list_networks: + filters: + - type: name + operator: eq + value: name1 + - type: name + operator: eq + value: name2 +""" + +RETURN = r""" +api_response: + description: Detailed information of all the networks listed. + type: dict + returned: always + contains: + data: + description: Contains detailed information about the network. + type: dict + contains: + created: + description: Timestamp in when the network was created. + type: int + returned: always + sample: 1689849698540 + created_by: + description: The UUID of the user who created the network. + type: str + returned: always + sample: "fdfsd-c9e7-4e1d-sdfef-sfrfds" + created_in_seconds: + description: Timestamp when the network was created. + type: int + returned: always + sample: 1689849698 + is_default: + description: Indicates whether this network is the default network. + type: bool + returned: always + sample: false + modified: + description: Timestamp when the network was last modified. + type: int + returned: always + sample: 1689849698540 + modified_by: + description: The UUID of the user who last modified the network. + type: str + returned: always + sample: "SFREVFGF6" + modified_in_seconds: + description: Timestamp when the network was last modified. + type: int + returned: always + sample: 1689849698 + name: + description: Name of the network. + type: str + returned: always + sample: "network_name" + owner_uuid: + description: UUID of the owner of the network. + type: str + returned: always + sample: "CRFRE" + scanner_count: + description: The count of scanners associated with this network. + type: int + returned: always + sample: 0 + uuid: + description: The unique identifier of the network. + type: str + returned: always + sample: "ssdfef-4f59-efgerg-b193-frtgtr" + pagination: + description: Pagination details of the response. + type: dict + contains: + limit: + description: The maximum number of records returned in the response. + type: int + returned: always + sample: 1 + offset: + description: The starting point from which records are returned. + type: int + returned: always + sample: 0 + sort: + description: List of sort conditions applied to the response. + type: list + elements: dict + contains: + name: + description: Name of the field by which the results are sorted. + type: str + returned: always + sample: "name" + order: + description: Order of sorting, ascending or descending. + type: str + returned: always + sample: "asc" + total: + description: Total number of items available. + type: int + returned: always + sample: 163 + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 + +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_special_filter +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "filters", "limit", "offset", "sort") + specific_spec = { + "include_deleted": {"required": False, "type": "bool"}, # unique for this module + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + def query_params(): + return add_custom_filters( + build_query_parameters(limit=module.params["limit"], offset=module.params["offset"]), + module.params["filters"], + handle_special_filter, + ) + + run_module(module, "networks", query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_plugin_families.py b/plugins/modules/list_plugin_families.py new file mode 100644 index 0000000..90ba45b --- /dev/null +++ b/plugins/modules/list_plugin_families.py @@ -0,0 +1,101 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_plugin_families +short_description: Returns the list of plugin families. +version_added: "0.0.1" +description: + - This module returns the list of plugin families. + - The module is made from https://developer.tenable.com/reference/io-plugins-families-list docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + all: + description: + - Specifies whether to return all plugin families. + - If true, the plugin families hidden in Tenable Vulnerability Management UI, for example, Port Scanners, are included in the list. + required: true + type: bool +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List all plugin families + list_plugin_families: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + all: true + +- name: List not all plugin families using enviroment creds + list_plugin_families: + all: false +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: always + contains: + data: + description: Contains data about different families of plugins or tools. + type: dict + contains: + families: + description: A list of family details retrieved from the API. + type: list + elements: dict + contains: + count: + description: The number of plugins or items within this family. + type: int + returned: always + sample: 1 + id: + description: Unique identifier for the family. + type: int + returned: always + sample: 1 + name: + description: Name of the family, describing the category or type. + type: str + returned: always + sample: "Windows : User management" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + special_args = {"all": {"required": True, "type": "bool"}} # unique for this module + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"plugins/families?all=/{module.params['all']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_plugin_in_familiy_id.py b/plugins/modules/list_plugin_in_familiy_id.py new file mode 100644 index 0000000..75958ad --- /dev/null +++ b/plugins/modules/list_plugin_in_familiy_id.py @@ -0,0 +1,105 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_plugin_in_familiy_id +short_description: Returns the list of plugins for the specified family ID. +version_added: "0.0.1" +description: + - This module returns the list of plugins for the specified family ID. + - The module is made from https://developer.tenable.com/reference/io-plugins-family-details-idt docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + id: + description: + - The ID of the plugin family you want to retrieve the list of plugins for. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + + +EXAMPLES = r""" +- name: List plugin in family + list_plugin_in_familiy_id: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + id: 123456 + +- name: List plugns in family using enviroment creds + list_plugin_families: + id: 123456 +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: always + contains: + data: + description: Contains data about a specific plugins. + type: dict + contains: + id: + description: Unique identifier of the plugin. + type: int + returned: always + sample: 1 + name: + description: Name of the category or group, describing the type of plugin. + type: str + returned: always + sample: "Windows : Problems" + plugins: + description: A list of plugins within the specified category. + type: list + elements: dict + contains: + id: + description: Unique identifier for the plugin. + type: int + returned: always + sample: 1 + name: + description: Name of the plugin. + type: str + returned: always + sample: "windows_name" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + special_args = {"id": {"required": True, "type": "str"}} + argument_spec = {**common_spec, **special_args} # unique for this module + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"plugins/families/{module.params['id']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_plugin_outputs.py b/plugins/modules/list_plugin_outputs.py new file mode 100644 index 0000000..e670b73 --- /dev/null +++ b/plugins/modules/list_plugin_outputs.py @@ -0,0 +1,299 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_plugin_outputs +short_description: Retrieves the vulnerability outputs for a plugin. +version_added: "0.0.1" +description: + - This module fetches detailed information about a specific plugin from Tenable.io. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Note This endpoint is not intended for large or frequent exports of data. + - This module was made from https://developer.tenable.com/reference/workbenches-vulnerability-output docs. +options: + plugin_id: + description: + - The ID of the plugin to retrieve information for. + - You can find the plugin ID by examining the output of list_vulnerabilities module. + required: true + type: int +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.date + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.filter_search_type +""" + +EXAMPLES = r""" +- name: Retrieve plugin details with specific filters + list_plugin_outputs: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + plugin_id: 123456 + filters: + - type: port.protocol + operator: eq + value: tcp + +- name: Get plugin details using environmental credentials + list_plugin_outputs: + plugin_id: 123456 +""" + + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: on success + contains: + data: + description: The data about the plugin and its associated vulnerabilities. + type: dict + contains: + info: + description: General information about the plugin and associated vulnerabilities. + type: dict + contains: + accepted_count: + description: The number of times the vulnerability has been accepted. + type: int + returned: always + sample: 0 + count: + description: The number of vulnerabilities associated with this plugin. + type: int + returned: always + sample: 1 + description: + description: Description of the plugin. + type: str + returned: always + sample: "This could be a long or short description." + discovery: + description: Information about when the vulnerability was first and last seen. + type: dict + contains: + seen_first: + description: The first time the vulnerability was seen. + type: str + returned: always + sample: "2023-07-03T09:23:22.907Z" + seen_last: + description: The last time the vulnerability was seen. + type: str + returned: always + sample: "2024-04-14T13:52:52.626Z" + plugin_details: + description: Specific details about the plugin. + type: dict + contains: + family: + description: The family to which the plugin belongs. + type: str + returned: always + sample: "Red Hat Local Security Checks" + modification_date: + description: The date when the plugin was last modified. + type: str + returned: always + sample: "2023-09-29T00:00:00Z" + name: + description: Name of the plugin. + type: str + returned: always + sample: "RHEL 7 : httpd (RHSA-2023:1593)" + publication_date: + description: The publication date of the plugin. + type: str + returned: always + sample: "2023-04-04T00:00:00Z" + severity: + description: The severity rating of the plugin. + type: int + returned: always + sample: 4 + type: + description: The type of the plugin, e.g., local or network. + type: str + returned: always + sample: "local" + version: + description: The version number of the plugin. + type: str + returned: always + sample: "1.4" + risk_information: + description: Detailed risk information associated with the plugin. + type: dict + contains: + cvss3_base_score: + description: The CVSS v3 base score of the vulnerability. + type: str + returned: always + sample: "9.8" + cvss3_temporal_score: + description: The CVSS v3 temporal score of the vulnerability. + type: str + returned: always + sample: "8.8" + cvss3_temporal_vector: + description: The CVSS v3 temporal vector of the vulnerability. + type: str + returned: always + sample: "E:P/RL:O/RC:C" + cvss3_vector: + description: The CVSS v3 vector of the vulnerability. + type: str + returned: always + sample: "AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + cvss_base_score: + description: The CVSS base score of the vulnerability. + type: str + returned: always + sample: "10.0" + cvss_temporal_score: + description: The CVSS temporal score of the vulnerability. + type: str + returned: always + sample: "7.8" + cvss_temporal_vector: + description: The CVSS temporal vector of the vulnerability. + type: str + returned: always + sample: "E:POC/RL:OF/RC:C" + cvss_vector: + description: The CVSS vector of the vulnerability. + type: str + returned: always + sample: "AV:N/AC:L/Au:N/C:C/I:C/A:C" + risk_factor: + description: The risk factor of the vulnerability. + type: str + returned: always + sample: "Critical" + stig_severity: + description: The STIG severity level of the vulnerability, if applicable. + type: str + returned: when available + sample: null + vulnerability_information: + description: Additional details about the vulnerabilities found. + type: dict + contains: + asset_inventory: + description: Indicates if the vulnerability is included in asset inventory. + type: bool + returned: always + sample: false + cpe: + description: List of CPE entries associated with the vulnerability. + type: list + elements: str + returned: always + sample: ["cpe:/o:redhat:enterprise_linux:7"] + default_account: + description: Indicates if a default account is associated with the vulnerability. + type: bool + returned: always + sample: false + exploit_available: + description: Indicates if an exploit is available for the vulnerability. + type: bool + returned: always + sample: true + exploit_frameworks: + description: List of exploit frameworks that apply to the vulnerability. + type: list + elements: str + returned: always + sample: [] + exploitability_ease: + description: A descriptive rating of how easy it is to exploit the vulnerability. + type: str + returned: always + sample: "Exploits are available" + exploited_by_malware: + description: Indicates if the vulnerability is known to be exploited by malware. + type: bool + returned: always + sample: false + exploited_by_nessus: + description: Indicates if Nessus has exploited the vulnerability. + type: bool + returned: always + sample: false + in_the_news: + description: Indicates if the vulnerability has been reported in the news. + type: bool + returned: always + sample: false + malware: + description: Indicates if malware is associated with the vulnerability. + type: bool + returned: always + sample: false + patch_publication_date: + description: The publication date of any patches for the vulnerability. + type: str + returned: always + sample: "2023-04-04T00:00:00Z" + unsupported_by_vendor: + description: Indicates if the vulnerability is unsupported by the vendor. + type: bool + returned: always + sample: false + vulnerability_publication_date: + description: The publication date of the vulnerability. + type: str + returned: always + sample: "2023-03-07T00:00:00Z" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "date_range", "filters", "filter_search_type") + specific_spec = { + "plugin_id": {"required": True, "type": "int"}, + } + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"workbenches/vulnerabilities/{module.params['plugin_id']}/outputs" + + def query_params(): + return add_custom_filters( + build_query_parameters( + date_range=module.params["date_range"], filter_search_type=module.params["filter_search_type"] + ), + module.params["filters"], + handle_multiple_filters, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_plugins.py b/plugins/modules/list_plugins.py new file mode 100644 index 0000000..a05ccb0 --- /dev/null +++ b/plugins/modules/list_plugins.py @@ -0,0 +1,315 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +module: list_plugins +short_description: Lists plugins in Tenable.io. +version_added: "0.0.1" +description: + - This module retrieves a list of plugin from Tenable.io + - Supporting complex filtering, sorting, pagination, and the option to include deleted plugin objects. + - Module made from https://developer.tenable.com/reference/was-v2-plugins-list docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + last_updated: + description: + - The last updated date to filter on in the YYYY-MM-DD format. + - Tenable Vulnerability Management returns only the plugins that have been updated after the specified date. + - His parameter does not take VPR updates into account. + - If you need to filter the plugin list by VPR update date, you should use a last_updated + - Date of 1970-01-01 to return all plugins, and then filter the result set manually based on the updated field in the vpr object. + required: false + type: str + size: + description: + - The number of records to include in the result set + - Default is 1000. The maximum size is 10000. + required: false + type: int + page: + description: + - The index of the page to return relative to the specified page size + - For example, to return records 10-19 with page size 10, you must specify page 2. If you omit this parameter. + - Tenable Vulnerability Management applies the default value of 1. + required: false + type: int +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List all plugins passing credentials to the module + list_plugins: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List one plugin record with enviroment credentials + list_plugins: + size: 1 + +- name: List four record plugins specifying page using enviorent credentials + list_plugins: + size: 4 + page: 3 +- name: List plugins filtering by last update date and passing credentials. + list_plugins: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + last_updated: 2023-01-01 + +- name: Use the three filters + list_plugins: + last_updated: 2024-01-01 + size: 1 + page: 1 + register: plugins +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: on success + contains: + data: + description: Contains detailed information about plugins. + type: dict + contains: + plugin_details: + description: A list of detailed plugin attributes. + type: list + elements: dict + contains: + attributes: + description: Attributes associated with each plugin. + type: dict + contains: + always_run: + description: Indicates if the plugin always runs regardless of the scan configuration. + type: bool + returned: always + sample: false + bid: + description: List of Bugtraq IDs associated with the plugin. + type: list + elements: str + returned: always + sample: [] + compliance: + description: Indicates if the plugin is used for compliance checks. + type: bool + returned: always + sample: false + cve: + description: List of CVE identifiers related to the plugin. + type: list + elements: str + returned: always + sample: [] + cvss_base_score: + description: CVSS base score of the vulnerabilities detected by the plugin. + type: float + returned: always + sample: 10 + cvss_vector: + description: Detailed CVSS vector information. + type: dict + contains: + AccessComplexity: + description: The complexity required for exploiting the vulnerability found. + type: str + returned: always + sample: "Low" + AccessVector: + description: Network access vector through which the vulnerability can be exploited. + type: str + returned: always + sample: "plugin" + Authentication: + description: Type of authentication needed to exploit the vulnerability. + type: str + returned: always + sample: "None required" + AvailabilityImpact: + description: The impact on availability due to the vulnerability. + type: str + returned: always + sample: "Complete" + ConfidentialityImpact: + description: The impact on confidentiality due to the vulnerability. + type: str + returned: always + sample: "Complete" + IntegrityImpact: + description: The impact on integrity due to the vulnerability. + type: str + returned: always + sample: "Complete" + raw: + description: The raw CVSS vector string. + type: str + returned: always + sample: "AV:N/AC:L/Au:N/C:C/I:C/A:C" + description: + description: Description of the plugin. + type: str + returned: always + sample: "This is a description." + exploit_available: + description: Indicates if an exploit is available for the vulnerabilities detected by the plugin. + type: bool + returned: always + sample: false + has_patch: + description: Indicates if a patch is available for the vulnerabilities detected by the plugin. + type: bool + returned: always + sample: false + intel_type: + description: Type of intelligence provided by the plugin, e.g., SENSOR. + type: str + returned: always + sample: "SENSOR" + plugin_modification_date: + description: Date when the plugin was last modified. + type: str + returned: always + sample: "2008-04-23T00:00:00Z" + plugin_publication_date: + description: Date when the plugin was published. + type: str + returned: always + sample: "1999-07-29T00:00:00Z" + plugin_type: + description: Type of the plugin, e.g., REMOTE. + type: str + returned: always + sample: "REMOTE" + plugin_version: + description: Version number of the plugin. + type: float + returned: always + sample: 1.36 + potential_vulnerability: + description: Indicates if the plugin potentially identifies vulnerabilities. + type: bool + returned: always + sample: true + risk_factor: + description: Risk factor as determined by the plugin. + type: str + returned: always + sample: "CRITICAL" + see_also: + description: List of URLs or references for more information. + type: list + elements: str + returned: always + sample: [] + solution: + description: Recommended solution or mitigation steps provided by the plugin. + type: str + returned: always + sample: "I am the solution." + synopsis: + description: Brief synopsis provided by the plugin. + type: str + returned: always + sample: "I am the synopsis." + vpr: + description: Vulnerability Priority Rating information if available. + type: dict + returned: when available + xref: + description: Cross-references to other security documentation. + type: list + elements: str + returned: always + sample: [] + xrefs: + description: Extended references, similar to xref. + type: list + elements: str + returned: always + sample: [] + id: + description: Unique identifier for the plugin. + type: int + returned: always + sample: 10024 + name: + description: Name of the plugin. + type: str + returned: always + sample: "BackOrifice Software Detection" + params: + description: Parameters associated with the request or the response. + type: dict + contains: + last_updated: + description: Date when the data was last updated. + type: str + returned: always + sample: "2024-01-01" + page: + description: Page number of the response data. + type: int + returned: always + sample: 1 + size: + description: Number of items per page in the response. + type: int + returned: always + sample: 1 + size: + description: The size of the data returned. + type: int + returned: always + sample: 1 + total_count: + description: Total number of items available in the database. + type: int + returned: always + sample: 26385 + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + # last_updated is unique in this module + # size is unique for this module + # page is unique for this module + common_spec = get_spec("access_key", "secret_key", "last_updated", "size", "page") + + module = AnsibleModule(argument_spec=common_spec, supports_check_mode=False) + + def query_params(): + return build_query_parameters( + last_updated=module.params["last_updated"], size=module.params["size"], page=module.params["page"] + ) + + endpoint = "plugins/plugin" + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_plugins_in_family_name.py b/plugins/modules/list_plugins_in_family_name.py new file mode 100644 index 0000000..bb5ccdc --- /dev/null +++ b/plugins/modules/list_plugins_in_family_name.py @@ -0,0 +1,107 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_plugins_in_family_name +short_description: Returns the list of plugins for the specified family name. +version_added: "0.0.1" +description: + - This module returns the list of plugins for the specified family name. + - The module is made from https://developer.tenable.com/reference/io-plugins-family-details-name docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + name: + description: + - The name of the plugin family you want to retrieve the list of plugins for. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List all plugin families + list_plugins_in_family_name: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Linux" + +- name: List not all plugin families using enviroment creds + list_plugins_in_family_name: + name: "Winwdows" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: always + contains: + data: + description: Contains data about a specific plugins. + type: dict + contains: + id: + description: Unique identifier of the plugin. + type: int + returned: always + sample: 1 + name: + description: Name of the category or group, describing the type of plugin. + type: str + returned: always + sample: "Windows : Problems" + plugins: + description: A list of plugins within the specified category. + type: list + elements: dict + contains: + id: + description: Unique identifier for the plugin. + type: int + returned: always + sample: 1 + name: + description: Name of the plugin. + type: str + returned: always + sample: "windows_name" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = {"name": {"required": True, "type": "str"}} # unique for this module and required + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "plugins/families/_byName" + + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_policies.py b/plugins/modules/list_policies.py new file mode 100644 index 0000000..29dd0a7 --- /dev/null +++ b/plugins/modules/list_policies.py @@ -0,0 +1,57 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_policies +short_description: Returns a list of policies (scan templates). +version_added: "0.0.1" +description: + - This module returns a list of policies (scan templates). + - Note Policies are referred to as scan templates in the new interface + - The term policy is only used in the classic interface. + - Requires STANDARD [32] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/policies-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List all policies in Tenable + list_policies: + access_key: "your_access_key" + secret_key: "your_secret_key" +- name: List all policies in Tenable using envirometn credentials + list_policies: +""" + +RETURN = r""" + +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "policies" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_remediation_scans.py b/plugins/modules/list_remediation_scans.py new file mode 100644 index 0000000..54f6366 --- /dev/null +++ b/plugins/modules/list_remediation_scans.py @@ -0,0 +1,142 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_remediation_scans +short_description: List possible filters for assets +version_added: "0.0.1" +description: + - Returns a list of remediation scans + - Note Keep in mind potential rate limits when using this endpoint + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-scans-remediation-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.generics +""" + +EXAMPLES = r""" +- name: List all remediation scans + list_remediation_scans: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List all remediation scans using enviroment creds + list_remediation_scans: + limit: 1 + sort: asc +""" + +RETURN = r""" +pagination: + description: Pagination details for the returned results. + returned: always + type: dict + contains: + offset: + description: The starting record of the result set. + type: int + total: + description: The total number of records. + type: int + sort: + description: The field and order used for sorting the results. + type: str + limit: + description: The number of records retrieved. + type: int +scans: + description: A list of remediation scans. + returned: always + type: list + elements: dict + contains: + type: + description: The type of the scan. + type: str + uuid: + description: The UUID of the scan. + type: str + permissions: + description: The permissions for the scan. + type: int + enabled: + description: Whether the scan is enabled. + type: bool + control: + description: Whether the scan is under control. + type: bool + read: + description: Whether the scan has been read. + type: bool + last_modification_date: + description: The last modification date of the scan. + type: int + creation_date: + description: The creation date of the scan. + type: int + status: + description: The status of the scan. + type: str + shared: + description: Whether the scan is shared. + type: bool + user_permissions: + description: The user permissions for the scan. + type: int + schedule_uuid: + description: The UUID of the scan schedule. + type: str + wizard_uuid: + description: The UUID of the scan wizard. + type: str + scan_creation_date: + description: The Unix timestamp when the remediation scan run was created. + type: int + owner: + description: The owner of the scan. + type: str + policy_id: + description: The policy ID of the scan. + type: int + id: + description: The unique ID of the scan. + type: int + name: + description: The name of the scan. + type: str +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "limit", "offset", "sort") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scans/remediation" + + def query_params(): + return build_query_parameters( + limit=module.params["limit"], offset=module.params["offset"], sort=module.params["sort"] + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_report_filters.py b/plugins/modules/list_report_filters.py new file mode 100644 index 0000000..23b38e8 --- /dev/null +++ b/plugins/modules/list_report_filters.py @@ -0,0 +1,112 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_report_filters +short_description: List filters for report. +version_added: "0.0.1" +description: + - This module list filters for reports + - Lists the filtering, sorting, and pagination capabilities available for agent records on endpoints that support them + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/vm-filters-reports-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List report filters + list_report_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List report file filters with enviroment creds + list_report_filters: +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "filters/reports/export" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_running_scans.py b/plugins/modules/list_running_scans.py new file mode 100644 index 0000000..3797bdc --- /dev/null +++ b/plugins/modules/list_running_scans.py @@ -0,0 +1,109 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: list_running_scans +short_description: Lists scans running on the requested scanner. +version_added: "0.0.1" +description: + - This module lists scans running on the requested scanner. + - The module is made from https://developer.tenable.com/reference/scanners-get-scans docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: List running scans on scanner + list_running_scans: + access_key: "your_access_key" + secret_key: "your_secret_key" + scanner_id: 11111 + +- name: List running scans on scanner on enviroment keys + list_running_scans: + scanner_id: 11111 +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + scans: + description: List of scans. + type: list + elements: dict + contains: + scan_id: + description: The ID of the scan. + type: int + sample: 36 + scanner_uuid: + description: The UUID of the scanner. + type: str + sample: "00000000-0000-0000-0000-00000000000000000000000000001" + name: + description: The name of the scan. + type: str + sample: "Basic Scan" + status: + description: The status of the scan. + type: str + sample: "pending" + id: + description: The unique identifier of the scan. + type: str + sample: "007d0e76-fcec-455d-a5e2-fac16772560c" + user: + description: The user who initiated the scan. + type: str + sample: "API Demo User" + user_uuid: + description: The UUID of the user who initiated the scan. + type: str + sample: "1fb43a88-2240-4d7e-a46a-dd655fa59398" + last_modification_date: + description: The last modification date of the scan. + type: int + sample: 1545945321 + start_time: + description: The start time of the scan. + type: int + sample: 1545945321 + network_id: + description: The network ID associated with the scan. + type: str + sample: "6be6cbfe-2c4b-4f3c-b959-127362d2dcce" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scanner_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scanners/{module.params['scanner_id']}/scans" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_scan_filters.py b/plugins/modules/list_scan_filters.py new file mode 100644 index 0000000..6a152fc --- /dev/null +++ b/plugins/modules/list_scan_filters.py @@ -0,0 +1,112 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_scan_filters +short_description: List filters for scans. +version_added: "0.0.1" +description: + - This module list filters for scans. + - Lists the filtering, sorting, and pagination capabilities available for agent records on endpoints that support them + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-filters-scan-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List scan filters + list_scan_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List tag asset filters with enviroment creds + list_scan_filters: +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "filters/scans/reports" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_scan_history_filters.py b/plugins/modules/list_scan_history_filters.py new file mode 100644 index 0000000..1368100 --- /dev/null +++ b/plugins/modules/list_scan_history_filters.py @@ -0,0 +1,112 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_scan_history_filters +short_description: List filters for scan history. +version_added: "0.0.1" +description: + - This module list filters for scan histories. + - Lists the filtering, sorting, and pagination capabilities available for agent records on endpoints that support them + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-filters-scan-history-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List scan history filters + list_scan_history_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List scan history filters with enviroment creds + list_scan_history_filters: +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "filters/scans/reports/history" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_scan_routes.py b/plugins/modules/list_scan_routes.py new file mode 100644 index 0000000..bf4b92d --- /dev/null +++ b/plugins/modules/list_scan_routes.py @@ -0,0 +1,73 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_scan_routes +short_description: List scan routes within a scanner group. +version_added: "0.0.1" +description: + - This module lists the hostnames, wildcards, IP addresses, and IP address ranges that Tenable Vulnerability + Management matches against targets in auto-routed scans. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module was made with https://developer.tenable.com/reference/io-scanner-groups-list-routes docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: List scan routes within a scanner group + list_scan_routes: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 124 + register: scan_routes_response + +- name: List scan routes within a scanner group with envirment creds + list_scan_routes: + group_id: 124 + register: scan_routes_response +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: os success + contains: + routes: + description: List of scan routes. + type: list + elements: dict + contains: + route: + description: The route (hostname, IP address, CIDR, etc.) + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanner-groups/{module.params['group_id']}/routes" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_scanner_group_details.py b/plugins/modules/list_scanner_group_details.py new file mode 100644 index 0000000..3341277 --- /dev/null +++ b/plugins/modules/list_scanner_group_details.py @@ -0,0 +1,118 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_scanner_group_details +short_description: Returns details for the specified scanner group. +version_added: "0.0.1" +description: + - This module returns details for the specified scanner group. + - Note This endpoint does not return details about scan routes configured for the scanner group. + For scan routes, use the GET /scanner-groups/group_id/routes endpoint instead. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/scanner-groups-details docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Get details of a scanner group + list_scanner_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 12345 + +- name: Get details of a scanner group using environment credentials + list_scanner_group_details: + group_id: 12345 +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + creation_date: + description: The creation date of the scanner group. + type: int + last_modification_date: + description: The last modification date of the scanner group. + type: int + owner_id: + description: The ID of the owner. + type: int + owner: + description: The name of the owner. + type: str + owner_uuid: + description: The UUID of the owner. + type: str + default_permissions: + description: The default permissions of the scanner group. + type: int + user_permissions: + description: The user permissions of the scanner group. + type: int + shared: + description: Indicates if the scanner group is shared. + type: int + scan_count: + description: The number of scans. + type: int + scanner_count: + description: The number of scanners in the group. + type: int + uuid: + description: The UUID of the scanner group. + type: str + type: + description: The type of the scanner group. + type: str + name: + description: The name of the scanner group. + type: str + network_name: + description: The name of the network. + type: str + id: + description: The ID of the scanner group. + type: int + scanner_id: + description: The ID of the scanner. + type: int + scanner_uuid: + description: The UUID of the scanner. + type: str + owner_name: + description: The name of the owner. + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanner-groups/{module.params['group_id']}" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_scanner_groups.py b/plugins/modules/list_scanner_groups.py new file mode 100644 index 0000000..1e1fcb0 --- /dev/null +++ b/plugins/modules/list_scanner_groups.py @@ -0,0 +1,111 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_scanner_groups +short_description: Lists scanner groups for your Tenable Vulnerability Management instance. +version_added: "0.0.1" +description: + - This module lists scanner groups for your Tenable Vulnerability Management instance. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List scanner groups + list_scanner_groups: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List scanner groups using enviroment creds + list_scanner_groups: +""" + +RETURN = r""" +scanner_pools: + description: A list of scanner groups. + type: list + elements: dict + returned: os success + contains: + creation_date: + description: The creation date of the scanner group. + type: int + last_modification_date: + description: The last modification date of the scanner group. + type: int + owner_id: + description: The ID of the owner of the scanner group. + type: int + owner: + description: The owner of the scanner group. + type: str + owner_uuid: + description: The UUID of the owner of the scanner group. + type: str + default_permissions: + description: The default permissions of the scanner group. + type: int + user_permissions: + description: The user permissions of the scanner group. + type: int + shared: + description: Indicates if the scanner group is shared. + type: int + scan_count: + description: The number of scans in the scanner group. + type: int + scanner_count: + description: The number of scanners in the scanner group. + type: int + uuid: + description: The UUID of the scanner group. + type: str + type: + description: The type of the scanner group. + type: str + name: + description: The name of the scanner group. + type: str + network_name: + description: The name of the network associated with the scanner group. + type: str + id: + description: The ID of the scanner group. + type: int + scanner_id: + description: The ID of the scanner. + type: int + scanner_uuid: + description: The UUID of the scanner. + type: str + owner_name: + description: The name of the owner of the scanner group. + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scanner-groups" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_scanners.py b/plugins/modules/list_scanners.py new file mode 100644 index 0000000..577c30d --- /dev/null +++ b/plugins/modules/list_scanners.py @@ -0,0 +1,251 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_scanners +short_description: Returns the scanner list. +version_added: "0.0.1" +description: + - This module returns the scanner list. + - Requires SCAN MANAGER [40] user permissions [40] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/scanners-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List all scanners + list_scanners: + access_key: "your_access_key" + secret_key: "your_secret_key" + +- name: List all scanners using envirometn credentials + list_scanners: + register: all_timezones +""" + +RETURN = r""" +api_response: + description: Detailed information returned by the Tenable API concerning scanners. + type: dict + returned: always + contains: + data: + description: Contains all relevant details about the scanners. + type: dict + contains: + scanners: + description: A list of scanner configurations. + type: list + elements: dict + contains: + creation_date: + description: UNIX timestamp when the scanner was created. + type: int + returned: always + sample: 123456 + distro: + description: Operating system distribution of the scanner. + type: str + returned: always + sample: "win-x86-64" + engine_version: + description: Version of the scanning engine used. + type: str + returned: always + sample: "1.1.1" + group: + description: Indicates whether the scanner is grouped with others. + type: bool + returned: always + sample: false + hostname: + description: Hostname of the scanner. + type: str + returned: always + sample: "name" + id: + description: Unique identifier of the scanner. + type: int + returned: always + sample: 317891 + ip_addresses: + description: List of IP addresses assigned to the scanner. + type: list + elements: str + returned: always + sample: ["10.200.16.23"] + key: + description: Key associated with the scanner for API or remote access. + type: str + returned: always + sample: "123456" + last_connect: + description: UNIX timestamp of the last connection made by the scanner. + type: int + returned: always + sample: 123456 + last_modification_date: + description: UNIX timestamp when the scanner configuration was last modified. + type: int + returned: always + sample: 123456 + linked: + description: Indicates if the scanner is linked to a central manager or cloud. + type: int + returned: always + sample: 1 + loaded_plugin_set: + description: Identifier of the plugin set loaded on the scanner. + type: str + returned: always + sample: "123456" + name: + description: Name of the scanner. + type: str + returned: always + sample: "name" + network_name: + description: Network name or identifier associated with the scanner. + type: str + returned: always + sample: "Default" + num_scans: + description: Number of scans performed by the scanner. + type: int + returned: always + sample: 0 + owner: + description: Username or identifier of the owner of the scanner. + type: str + returned: always + sample: "system" + owner_id: + description: Numeric identifier of the owner. + type: int + returned: always + sample: 2297093 + owner_name: + description: Name of the owner. + type: str + returned: always + sample: "system" + owner_uuid: + description: UUID of the owner. + type: str + returned: always + sample: "123456" + platform: + description: Platform of the scanner, such as WINDOWS or LINUX. + type: str + returned: always + sample: "WINDOWS" + pool: + description: Indicates if the scanner is part of a scanner pool. + type: bool + returned: always + sample: false + remote_uuid: + description: Remote UUID for the scanner. + type: str + returned: always + sample: "123456" + scan_count: + description: Total number of scans initiated by this scanner. + type: int + returned: always + sample: 0 + shared: + description: Indicates if the scanner is shared with other users. + type: int + returned: always + sample: 1 + source: + description: Source of the scanner configuration, typically 'service' or 'manual'. + type: str + returned: always + sample: "service" + status: + description: Current status of the scanner, such as 'on' or 'off'. + type: str + returned: always + sample: "on" + supports_remote_logs: + description: Indicates if the scanner supports remote logging. + type: bool + returned: always + sample: true + supports_remote_settings: + description: Indicates if the scanner can be configured remotely. + type: bool + returned: always + sample: true + supports_webapp: + description: Indicates if the scanner supports web application scanning. + type: bool + returned: always + sample: false + timestamp: + description: UNIX timestamp of the last update to the scanner's status or configuration. + type: int + returned: always + sample: 1714253297 + type: + description: Type of the scanner, such as 'managed' or 'standalone'. + type: str + returned: always + sample: "managed" + ui_build: + description: Build number of the scanner's user interface. + type: str + returned: always + sample: "29" + ui_version: + description: Version number of the scanner's user interface. + type: str + returned: always + sample: "10.7.2" + user_permissions: + description: Permissions level of the user regarding this scanner. + type: int + returned: always + sample: 123456 + uuid: + description: Universal Unique Identifier of the scanner. + type: str + returned: always + sample: "123456" + status_code: + description: HTTP status code returned by the API, indicating the result of the request. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "scanners" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_scanners_within_scanner_group.py b/plugins/modules/list_scanners_within_scanner_group.py new file mode 100644 index 0000000..80c0e94 --- /dev/null +++ b/plugins/modules/list_scanners_within_scanner_group.py @@ -0,0 +1,129 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_scanners_within_scanner_group +short_description: Lists scanners associated with the scanner group. +version_added: "0.0.1" +description: + - This module lists scanners associated with the scanner group. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/scanner-groups-list-scanners . +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: List scanners within a scanner group + list_scanners_within_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 12345 + +- name: List scanners within a scanner group using enviroment creds + list_scanners_within_scanner_group: + group_id: 12345 +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + scanners: + description: List of scanners in the scanner group. + type: list + elements: dict + contains: + creation_date: + description: The creation date of the scanner. + type: int + group: + description: Indicates if the scanner is part of a group. + type: bool + id: + description: The ID of the scanner. + type: int + key: + description: The key of the scanner. + type: str + last_connect: + description: The last connection date of the scanner. + type: int + last_modification_date: + description: The last modification date of the scanner. + type: int + license: + description: License information of the scanner. + type: dict + linked: + description: Indicates if the scanner is linked. + type: int + name: + description: The name of the scanner. + type: str + num_scans: + description: The number of scans run by the scanner. + type: int + owner: + description: The owner of the scanner. + type: str + owner_id: + description: The ID of the owner. + type: int + owner_name: + description: The name of the owner. + type: str + owner_uuid: + description: The UUID of the owner. + type: str + pool: + description: Indicates if the scanner is a pool. + type: bool + scan_count: + description: The count of scans performed. + type: int + source: + description: The source of the scanner. + type: str + status: + description: The status of the scanner. + type: str + timestamp: + description: The timestamp of the scanner. + type: int + type: + description: The type of the scanner. + type: str + uuid: + description: The UUID of the scanner. + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanner-groups/{module.params['group_id']}/scanners" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_scans.py b/plugins/modules/list_scans.py new file mode 100644 index 0000000..a57b44f --- /dev/null +++ b/plugins/modules/list_scans.py @@ -0,0 +1,270 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_scans +short_description: Returns a list of scans. +version_added: "0.0.1" +description: + - This module returns a list of scans. + - Module made from https://developer.tenable.com/reference/scans-list docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Keep in mind potential rate limits when using this endpoint. + - To check the status of your scans, use the GET /scans/{scan_id}/latest-status endpoint. + - Tenable recommends the GET /scans/{scan_id}/latest-status endpoint especially if you are programmatically checking the status of large numbers of scans. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.last_modification_date + - valkiriaaquatica.tenable.folder +""" + + +EXAMPLES = r""" +- name: List all scans using enviroment creds + list_scans: + register: all_scans + +- name: List scan from folder and using enviroment creds + list_scans: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: 123456 +""" + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + type: dict + returned: always + contains: + data: + description: Contains detailed information about the response's data. + type: dict + contains: + scanners: + description: A list of scanners details retrieved from the API. + type: list + elements: dict + contains: + creation_date: + description: The timestamp when the scanner was created. + type: int + returned: always + sample: 123456 + distro: + description: Operating system distribution of the scanner. + type: str + returned: always + sample: "win-x86-64" + engine_version: + description: Version of the scanning engine. + type: str + returned: always + sample: "1.1.1" + group: + description: Indicates whether the scanner is part of a group. + type: bool + returned: always + sample: false + hostname: + description: The hostname of the scanner. + type: str + returned: always + sample: "name" + id: + description: Unique identifier for the scanner. + type: int + returned: always + sample: 317891 + ip_addresses: + description: List of IP addresses associated with the scanner. + type: list + elements: str + returned: always + sample: ["10.200.16.23"] + key: + description: Unique key or identifier for the scanner. + type: str + returned: always + sample: "123456" + last_connect: + description: The last timestamp when the scanner connected to the network. + type: int + returned: always + sample: 123456 + last_modification_date: + description: The timestamp when the scanner was last modified. + type: int + returned: always + sample: 123456 + linked: + description: Indicates if the scanner is linked with the central system. + type: int + returned: always + sample: 1 + loaded_plugin_set: + description: Identifier for the set of plugins loaded on the scanner. + type: str + returned: always + sample: "123456" + name: + description: The name of the scanner. + type: str + returned: always + sample: "name" + network_name: + description: The network name associated with the scanner. + type: str + returned: always + sample: "Default" + num_scans: + description: The number of scans conducted by the scanner. + type: int + returned: always + sample: 0 + owner: + description: The system account that owns the scanner. + type: str + returned: always + sample: "system" + owner_id: + description: The system-generated ID of the owner. + type: int + returned: always + sample: 2297093 + owner_name: + description: The name of the owner. + type: str + returned: always + sample: "system" + owner_uuid: + description: The UUID of the owner. + type: str + returned: always + sample: "123456" + platform: + description: The platform on which the scanner operates, e.g., WINDOWS. + type: str + returned: always + sample: "WINDOWS" + pool: + description: Indicates if the scanner is part of a pool. + type: bool + returned: always + sample: false + remote_uuid: + description: Remote UUID associated with the scanner. + type: str + returned: always + sample: "123456" + scan_count: + description: The count of scans performed by the scanner. + type: int + returned: always + sample: 0 + shared: + description: Indicates if the scanner is shared among multiple users. + type: int + returned: always + sample: 1 + source: + description: The source from which the scanner was provisioned. + type: str + returned: always + sample: "service" + status: + description: The current status of the scanner, e.g., on or off. + type: str + returned: always + sample: "on" + supports_remote_logs: + description: Indicates whether the scanner supports remote logs. + type: bool + returned: always + sample: true + supports_remote_settings: + description: Indicates whether the scanner supports remote settings. + type: bool + returned: always + sample: true + supports_webapp: + description: Indicates whether the scanner supports web application scanning. + type: bool + returned: always + sample: false + timestamp: + description: Timestamp indicating the last significant update or event for the scanner. + type: int + returned: always + sample: 1714253297 + type: + description: The type of scanner, e.g., managed or independent. + type: str + returned: always + sample: "managed" + ui_build: + description: The build number of the user interface on the scanner. + type: str + returned: always + sample: "29" + ui_version: + description: The version number of the user interface on the scanner. + type: str + returned: always + sample: "10.7.2" + user_permissions: + description: Numeric value representing the permissions of the user regarding this scanner. + type: int + returned: always + sample: 123456 + uuid: + description: The UUID of the scanner. + type: str + returned: always + sample: "123456" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "folder_id", "last_modification_date") + special_args = { + "folder_id": {"required": False, "type": "int"}, + "last_modification_date": {"required": False, "type": "int"}, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "scans" + + def query_params(): + return build_query_parameters( + folder_id=module.params["folder_id"], + last_modification_date=module.params["last_modification_date"], + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_tag_categories.py b/plugins/modules/list_tag_categories.py new file mode 100644 index 0000000..d50c8fb --- /dev/null +++ b/plugins/modules/list_tag_categories.py @@ -0,0 +1,166 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +module: list_tag_categories +short_description: Returns a list of tag categories. +version_added: "0.0.1" +description: + - This module retrieves a list of tag categories from Tenable.io + - Supporting complex filtering,date, offset and limit. + - Module made from https://developer.tenable.com/reference/tags-list-tag-categoriesdocs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.filter_type + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.generics +""" + + +EXAMPLES = r""" +- name: List tag categories using enviroment creds + list_tag_categories: + +- name: List tag categories using enviroment creds + list_tag_categories: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + limit: 1 + +- name: List tag categories using enviroment creds and filtering + list_tag_categories: + filters: + - type: name + operator: eq + value: name +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains detailed information about network categories. + type: dict + contains: + categories: + description: A list of categories within the networks. + type: list + elements: dict + contains: + created_at: + description: The date and time when the category was created. + type: str + returned: always + sample: "2023-01-01T00:00:00Z" + created_by: + description: Email of the user who created the category. + type: str + returned: always + sample: "email@email.com" + description: + description: Description of the category. + type: str + returned: always + sample: "" + name: + description: Name of the category. + type: str + returned: always + sample: "name" + product: + description: Product identifier associated with the category. + type: str + returned: always + sample: "1" + reserved: + description: Indicates if the category is reserved. + type: bool + returned: always + sample: false + updated_at: + description: The date and time when the category was last updated. + type: str + returned: always + sample: "2023-01-02T00:00:00Z" + updated_by: + description: Email of the user who last updated the category. + type: str + returned: always + sample: "autobit@nttdata.com" + uuid: + description: UUID of the category. + type: str + returned: always + sample: "12356" + pagination: + description: Pagination details of the list. + type: dict + contains: + limit: + description: The maximum number of records returned per page. + type: int + returned: always + sample: 5000 + offset: + description: The offset from which records start. + type: int + returned: always + sample: 0 + total: + description: The total number of records available. + type: int + returned: always + sample: 1 + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_special_filter +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "filters", "filter_type", "limit", "offset", "sort") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "tags/categories" + + def query_params(): + return add_custom_filters( + build_query_parameters( + limit=module.params["limit"], + offset=module.params["offset"], + sort=module.params["sort"], + filter_type=module.params["filter_type"], + ), + module.params["filters"], # this is handled by handle_special_filter + handle_special_filter, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_tag_values.py b/plugins/modules/list_tag_values.py new file mode 100644 index 0000000..6cb17b2 --- /dev/null +++ b/plugins/modules/list_tag_values.py @@ -0,0 +1,236 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_tag_values +short_description: Returns a list of tag values. +version_added: "0.0.1" +description: + - This module retrieves a list of agents from Tenable.io + - The list can also include the tag categories that do not have any associated values. + - Supporting complex filtering,wilcarding, sorting, limit. + - Module made from https://developer.tenable.com/reference/tags-list-tag-values docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.filters + - valkiriaaquatica.tenable.filter_type + - valkiriaaquatica.tenable.filters_wildcards + - valkiriaaquatica.tenable.generics +""" + + +EXAMPLES = r""" +- name: List tag values that in their value contain dev using enviroment creds and limit 1 + list_tag_values: + filters: + - type: value + operator: match + value: dev + limit: 1 + +- name: List tag values that the value is equal to Production and theis description is not equal to Test passing creds as variables + list_tag_values: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filter_type: and + filters: + - type: value + operator: eq + value: Production + - type: description + operator: neq + value: "Test" + +- name: List tag values that in their value exists dev using credentials as variables + list_tag_values: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + wildcard_text: financ + wildcard_fields: category_name +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains detailed information about tag values and their attributes. + type: dict + contains: + pagination: + description: Pagination details of the list. + type: dict + contains: + limit: + description: The maximum number of records returned per page. + type: int + returned: always + sample: 1 + offset: + description: The offset from which records start. + type: int + returned: always + sample: 0 + total: + description: The total number of records available. + type: int + returned: always + sample: 516 + values: + description: A list of tag values. + type: list + elements: dict + contains: + access_control: + description: Access control settings for the current user regarding this tag. + type: dict + contains: + current_user_permissions: + description: Permissions of the current user on this tag. + type: list + elements: str + returned: always + sample: ["CAN_USE"] + category_description: + description: Description of the tag category. + type: str + returned: always + sample: "name" + category_name: + description: Name of the tag category. + type: str + returned: always + sample: "name" + category_uuid: + description: UUID of the tag category. + type: str + returned: always + sample: "123456" + consecutive_error_count: + description: Count of consecutive errors encountered by this tag. + type: int + returned: always + sample: 0 + created_at: + description: Date and time when the tag was created. + type: str + returned: always + sample: "2023-01-01T00:00:00Z" + created_by: + description: Email of the user who created the tag. + type: str + returned: always + sample: "email@email.com" + description: + description: Description of the tag. + type: str + returned: always + sample: "name" + processed_at: + description: Date and time when the tag was last processed. + type: str + returned: always + sample: "2023-01-02T00:00:00Z" + processing_status: + description: Current processing status of the tag. + type: str + returned: always + sample: "COMPLETE" + product: + description: Product associated with the tag. + type: str + returned: always + sample: "IO" + saved_search: + description: Indicates if this tag is part of a saved search. + type: bool + returned: always + sample: false + type: + description: Type of the tag, e.g., dynamic or static. + type: str + returned: always + sample: "dynamic" + updated_at: + description: Date and time when the tag was last updated. + type: str + returned: always + sample: "2023-01-02T00:00:00Z" + updated_by: + description: Email of the user who last updated the tag. + type: str + returned: always + sample: "email@email.com" + uuid: + description: UUID of the tag. + type: str + returned: always + sample: "123456" + value: + description: Value of the tag. + type: str + returned: always + sample: "name" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_special_filter +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec( + "access_key", + "secret_key", + "filter_type", + "filters", + "wildcard_fields", + "wildcard_text", + "limit", + "offset", + "sort", + ) + module = AnsibleModule(argument_spec=common_spec, supports_check_mode=False) + endpoint = "tags/values" + + def query_params(): + return add_custom_filters( + build_query_parameters( + filter_type=module.params["filter_type"], + wildcard_text=module.params["wildcard_text"], + wildcard_fields=module.params["wildcard_fields"], + limit=module.params["limit"], + offset=module.params["offset"], + sort=module.params["sort"], + ), + module.params["filters"], + handle_special_filter, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_tags_for_an_asset.py b/plugins/modules/list_tags_for_an_asset.py new file mode 100644 index 0000000..59c1a3b --- /dev/null +++ b/plugins/modules/list_tags_for_an_asset.py @@ -0,0 +1,121 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +module: list_tags_for_an_asset +short_description: Returns the tags an asset has. +version_added: "0.0.1" +description: + - This module returns a list of assigned tags for an asset specified by UUID. + - Module made from https://developer.tenable.com/reference/tags-list-asset-tags docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.asset +""" + + +EXAMPLES = r""" +- name: List tags for an assset using enviroment creds + list_tags_for_an_asset: + asset_uuid: 123456 +- name: List tags for an assset passing vars + list_tags_for_an_asset: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: 123456 +""" + + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains detailed information about tags of the asset + type: dict + contains: + tags: + description: A list of tags associated with the asset. + type: list + elements: dict + contains: + asset_uuid: + description: UUID of the asset associated with the tag. + type: str + returned: always + sample: "123456" + category_name: + description: Name of the category to which the tag belongs. + type: str + returned: always + sample: "Department" + category_uuid: + description: UUID of the category to which the tag belongs. + type: str + returned: always + sample: "987654" + container_uuid: + description: UUID of the container where the tag is stored. + type: str + returned: always + sample: "123789" + created_at: + description: Timestamp when the tag was created. + type: str + returned: always + sample: "ci" + product: + description: Product type associated with the tag. + type: str + returned: always + sample: "IO" + source: + description: Source of the tag, indicating how it was created. + type: str + returned: always + sample: "dynamic" + value: + description: Value of the tag. + type: str + returned: always + sample: "value" + value_uuid: + description: UUID of the tag value. + type: str + returned: always + sample: "123456" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "asset_uuid") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + asset_uuid = module.params["asset_uuid"] + endpoint = f"tags/assets/{asset_uuid}/assignments" + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_templates.py b/plugins/modules/list_templates.py new file mode 100644 index 0000000..c5fa42e --- /dev/null +++ b/plugins/modules/list_templates.py @@ -0,0 +1,149 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_templates +short_description: Lists Tenable-provided scan templates +version_added: "0.0.1" +description: + - This module lists Tenable-provided scan templates + - Tenable provides a number of scan templates to facilitate the creation of scans and scan policies. + - The module is made from https://developer.tenable.com/reference/editor-list-templates docs. + - Requires STANDARD [32] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan_templates +""" + +EXAMPLES = r""" +- name: List scan templates + list_templates: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + type: "scan" + +- name: List policy templates usnig enviroment creds + list_templates: + type: "policy" + +- name: List remediation templates + list_templates: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + type: "remediation" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains detailed information about the templates. + type: dict + contains: + templates: + description: A list of template details. + type: list + elements: dict + contains: + cloud_only: + description: Indicates if the template is exclusively for cloud environments. + type: bool + returned: always + sample: false + desc: + description: Description of the template. + type: str + returned: always + sample: "desc" + icon: + description: Icon representation of the template. + type: str + returned: always + sample: "P" + is_agent: + description: Specifies whether the template is meant for agent-based scans. + type: bool + returned: always + sample: null + is_was: + description: Specifies whether the template is meant for web application scanning. + type: bool + returned: always + sample: null + manager_only: + description: Indicates if the template is restricted to manager-level users only. + type: bool + returned: always + sample: false + name: + description: Name of the template. + type: str + returned: always + sample: "advanced" + order: + description: Display order of the template in listings. + type: int + returned: always + sample: 2 + subscription_only: + description: Indicates if the template is available only to subscribed users. + type: bool + returned: always + sample: false + title: + description: Title of the template. + type: str + returned: always + sample: "title" + unsupported: + description: Indicates if the template is unsupported. + type: bool + returned: always + sample: false + uuid: + description: UUID of the template. + type: str + returned: always + sample: "123456" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "type") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if module.params["type"] == "scan": + endpoint = "editor/scan/templates" + if module.params["type"] == "policy": + endpoint = "editor/policy/templates" + if module.params["type"] == "remediation": + endpoint = "editor/remediation/templates" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_users_in_group.py b/plugins/modules/list_users_in_group.py new file mode 100644 index 0000000..6d80a74 --- /dev/null +++ b/plugins/modules/list_users_in_group.py @@ -0,0 +1,108 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_users_in_group +short_description: Return the group user list. +version_added: "0.0.1" +description: + - This module returns the list of users in a specified group in Tenable.io. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/groups-list-users docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: List users in a group using explicit credentials + list_users_in_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 1233 + +- name: List users in a group using environment creds + list_users_in_group: + group_id: 1233 +""" + +RETURN = r""" +api_response: + description: Response from the Tenable.io API. + type: dict + returned: always + contains: + users: + description: List of users in the group. + type: list + elements: dict + contains: + id: + description: Unique ID of the user. + type: int + user_name: + description: Username of the user. + type: str + username: + description: Username of the user. + type: str + email: + description: Email of the user. + type: str + name: + description: Name of the user. + type: str + type: + description: Type of the user. + type: str + permissions: + description: Permissions assigned to the user. + type: int + last_login_attempt: + description: Timestamp of the last login attempt. + type: int + login_fail_count: + description: Number of login failures. + type: int + login_fail_total: + description: Total number of login failures. + type: int + enabled: + description: Whether the user is enabled or not. + type: bool + uuid: + description: UUID of the user. + type: str + container_uuid: + description: Container UUID of the user. + type: str + uuid_id: + description: UUID ID of the user. + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"groups/{module.params['group_id']}/users" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_vulnerabilities.py b/plugins/modules/list_vulnerabilities.py new file mode 100644 index 0000000..44929b4 --- /dev/null +++ b/plugins/modules/list_vulnerabilities.py @@ -0,0 +1,192 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_vulnerabilities +short_description: Returns a list of recorded vulnerabilities. +version_added: "0.0.1" +description: + - This modules returns a list of assets and their vulnerabilities associated. + - Filters can be applied. + - The list returned is limited to 5,000. To retrieve more than 5,000 vulnerabilities, use the export_workbench module (deprecated). + - Additionally, this endpoint only returns data less than 450 days (15 months) old. + - Note This endpoint is not intended for large or frequent exports of vulnerability or assets data. + - Tenable recommends the export_vulnerabilities module or https://developer.tenable.com/reference/exports-vulns-request-export for large amount. + - For information and best practices for retrieving vulnerability see https://developer.tenable.com/docs/retrieve-vulnerability-data-from-tenableio + - For information and best practices for retrieving assets see https://developer.tenable.com/docs/retrieve-asset-data-from-tenableio + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/workbenches-vulnerabilities docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.date + - valkiriaaquatica.tenable.filter_search_type + - valkiriaaquatica.tenable.filters +""" + + +EXAMPLES = r""" +- name: List vulnerabilities with fiilters and date range + list_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + date_range: 2 + filters: + - type: plugin.attributes.vpr.score + operator: eq + value: "5" + +- name: List vulnerabilities with two condicional and filters and using enviroment creds + list_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: plugin.attributes.vpr.score + operator: eq + value: "5" + - type: tracking.state + operator: neq + value: "Fixed" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains detailed information about the assets and vulnerabilities. + type: dict + contains: + total_asset_count: + description: The total number of assets. + type: int + returned: always + sample: 0 + total_vulnerability_count: + description: The total number of vulnerabilities found across all assets. + type: int + returned: always + sample: 1 + vulnerabilities: + description: A list of detailed vulnerability information. + type: list + elements: dict + contains: + accepted_count: + description: The number of vulnerabilities that have been accepted as manageable risks. + type: int + returned: always + sample: 0 + count: + description: The number of instances this particular vulnerability has been found. + type: int + returned: always + sample: 1 + counts_by_severity: + description: A list of counts of vulnerabilities classified by their severity. + type: list + elements: dict + contains: + count: + description: The count of vulnerabilities for the particular severity level. + type: int + returned: always + sample: 1 + value: + description: The severity level value. + type: int + returned: always + sample: 1 + cvss3_base_score: + description: The CVSS version 3 base score of the vulnerability. + type: float + returned: always + sample: 6.5 + cvss_base_score: + description: The CVSS version 2 base score of the vulnerability. + type: float + returned: always + sample: 6.4 + plugin_family: + description: The family of the plugin that identified the vulnerability. + type: str + returned: always + sample: "amazon linux" + plugin_id: + description: The unique identifier of the plugin that identified the vulnerability. + type: int + returned: always + sample: 123456 + plugin_name: + description: The name of the plugin that identified the vulnerability. + type: str + returned: always + sample: "amazon linux)" + recasted_count: + description: The number of vulnerabilities that have been recast or reclassified by the user. + type: int + returned: always + sample: 0 + severity: + description: The severity rating of the vulnerability. + type: int + returned: always + sample: 2 + vpr_score: + description: Tenable's Vulnerability Priority Rating (VPR) score for the vulnerability. + type: float + returned: always + sample: 5 + vulnerability_state: + description: The current state of the vulnerability, ex, active, solved. + type: str + returned: always + sample: "Active" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "filters", "filter_search_type", "date_range") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "workbenches/vulnerabilities" + + def query_params(): + return add_custom_filters( + build_query_parameters( + date_range=module.params["date_range"], + filter_search_type=module.params["filter_search_type"], + ), + module.params["filters"], + handle_multiple_filters, + ) + + run_module(module, endpoint, query_params_func=query_params, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_vulnerability_filters.py b/plugins/modules/list_vulnerability_filters.py new file mode 100644 index 0000000..9d38c00 --- /dev/null +++ b/plugins/modules/list_vulnerability_filters.py @@ -0,0 +1,112 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_vulnerability_filters +short_description: List filters for vulnerabilities +version_added: "0.0.1" +description: + - This module list filters for vulnerabilities + - Lists the filtering, sorting, and pagination capabilities available for agent records on endpoints that support them + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-filters-vulnerabilities-workbench-list docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List vulnerability filters + list_vulnerability_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + +- name: List vulnerability filters with enviroment creds + list_agent_filters: +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = "filters/workbenches/vulnerabilities" + + run_module(module, endpoint, method="GET") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/list_vulnerability_filters_vtwo.py b/plugins/modules/list_vulnerability_filters_vtwo.py new file mode 100644 index 0000000..a64e594 --- /dev/null +++ b/plugins/modules/list_vulnerability_filters_vtwo.py @@ -0,0 +1,131 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: list_vulnerability_filters_vtwo +short_description: Returns the filters available for the Vulnerabilities Workbench with tags. +version_added: "0.0.1" +description: + - This module returns the filters available for the Vulnerabilities Workbench iwht tags. + - For more information about these filters, see https://developer.tenable.com/docs/workbench-filters + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/io-filters-vulnerabilities-workbench-list-v2 docs. +options: + tag_uuids: + description: + - A list of tag UUIDs that are guaranteed to be returned in the initial data set of the tag_uuid filter, if the tags exist. + required: true + type: list + elements: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: List vulnerability filters + list_vulnerability_filters_vtwo: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + tag_uuids: + - 123456 + +- name: List vulnerability filters with enviroment creds + list_vulnerability_filters_vtwo: + tag_uuids: + - 123456 + - 987654 +""" + + +RETURN = r""" +api_response: + description: Detailed information returned by the API. + returned: always + type: complex + contains: + data: + description: The main data content returned by the API. + type: dict + returned: always + contains: + filters: + description: A list of filter objects that define metadata for asset tagging. + type: list + returned: always + elements: dict + contains: + name: + description: The name of the filter or tag attribute. + type: str + returned: always + operators: + description: List of applicable operators for the filter. + type: list + elements: str + returned: always + readable_name: + description: A human-readable name for the filter. + type: str + returned: always + control: + description: Defines the control type and options for the filter, applicable for dropdown or multiple choice types. + type: dict + returned: optional + contains: + list: + description: List of options available under this control. + type: list + elements: dict + contains: + name: + description: The internal name of the option. + type: str + returned: always + value: + description: The value associated with this option. + type: str + returned: always + type: + description: Type of control for the UI element. + type: str + returned: always + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "tag_uuids": {"required": True, "type": "list", "elements": "str"} + } # unique for this module and required + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "filters/workbenches/vulnerabilities" + + payload_keys = ["tag_uuids"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/move_assets.py b/plugins/modules/move_assets.py new file mode 100644 index 0000000..ce0ba48 --- /dev/null +++ b/plugins/modules/move_assets.py @@ -0,0 +1,131 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: move_assets +short_description: Moves assets from the specified network to another network +version_added: "0.0.1" +description: + - This module moves assets from the specified network to another network + - You can use this endpoint to move assets from the default network to a user-defined network + - You can use this endpoint to move assets from a user-defined network to the default network + - You can use this endpoint to move assets from one user-defined network to another user-defined network + - This request creates an asynchronous job in Tenable Vulnerability Management. + - This module is made from https://developer.tenable.com/reference/assets-bulk-move docs. + - Requires SCAN OPERATOR [24] user permissions as specified in the Tenable.io API documentation. +options: + source: + description: + - The UUID of the network currently associated with the assets. + - Use list_networks module to list the networks and get the uuid. + required: true + type: str + destination: + description: + - The UUID of the network to associate with the specified assets. + - Use list_networks module to list the networks and get the uuid. + required: true + type: str + targets: + description: + - The IPv4 addresses of the assets to move. + - The addresses can be represented as a comma-separated list "192.168.1.10,10.10.4.2" + - The addresses can be represented as a CIDR "1.1.1.0/24" + - The addresses can be represented as a range "2.2.2.2-2.2.2.200" + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + + +EXAMPLES = r""" +- name: Move one asset network and enviroment creds + move_assets: + source: "123456" + destinatation: "654321" + targets: "192.168.1.120" + +- name: Move a range of assets network passing variable creds + move_assets: + access_key: "your_access_key" + secret_key: "your_secret_key" + source: "123456" + destinatation: "654321" + targets: "10.10.10.1-10.10.10.80" + +- name: Move a whole CIDR of assets + move_assets: + source: "123456" + destinatation: "654321" + targets: "10.10.10.0/24" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response moving 1 asset. + type: dict + returned: always + contains: + data: + description: Contains the response data for the moving operation. + type: dict + contains: + response: + description: Nested response data. + type: dict + contains: + data: + description: Further nested data providing details about the asset movement. + type: dict + contains: + asset_count: + description: The number of assets that were moved. + type: int + returned: always + sample: 1 + status_code: + description: HTTP status code returned by the API after the asset movement. + type: int + returned: always + sample: 202 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + # specified arguments for this module + specific_spec = { + "source": {"required": True, "type": "str"}, + "destination": {"required": True, "type": "str"}, + "targets": {"required": True, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "api/v2/assets/bulk-jobs/move-to-network" + + payload_keys = ["source", "destination", "targets"] + payload = build_payload(module, payload_keys) + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/pause_scan.py b/plugins/modules/pause_scan.py new file mode 100644 index 0000000..f52bc17 --- /dev/null +++ b/plugins/modules/pause_scan.py @@ -0,0 +1,62 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: pause_scan +short_description: Pauses a scan +version_added: "0.0.1" +description: + - This module pauses a scan + - You can only pause scans that have a running status. + - The module is made from https://developer.tenable.com/reference/scans-pause docs. + - Requires SCAN OPERATOR [24] and CAN EXECUTE [32] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Create folder using enviroment creds + pause_scan: + scan_id: "123456" + +- name: Create folder passing creds as vars + pause_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" +""" + + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scan_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/pause" + + run_module(module, endpoint, method="POST") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/remove_agent_from_group.py b/plugins/modules/remove_agent_from_group.py new file mode 100644 index 0000000..1733baa --- /dev/null +++ b/plugins/modules/remove_agent_from_group.py @@ -0,0 +1,68 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: remove_agent_from_group +short_description: Remove an agent from an agent group. +version_added: "0.0.1" +description: + - This module removes an agent from the specified agent group. + - This module is made from https://developer.tenable.com/reference/agent-groups-delete-agent docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.agent_group + - valkiriaaquatica.tenable.agent +""" + +EXAMPLES = r""" +- name: Remove agent to from aagent group + remove_agent_from_group: + access_key: "your_access_key" + secret_key: "your_secret_key" + group_id: "123456" + agent_id: "654321" + +- name: Remove an agent from an agent group using enviroment vars + remove_agent_from_group: + group_id: "{{ tenable_group_id }}" + agent_id: "{{ tenable_agent_id }}" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "agent_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agent-groups/{module.params['group_id']}/agents/{module.params['agent_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/remove_agents_from_group.py b/plugins/modules/remove_agents_from_group.py new file mode 100644 index 0000000..71e4a6b --- /dev/null +++ b/plugins/modules/remove_agents_from_group.py @@ -0,0 +1,102 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: remove_agents_from_group +short_description: Creates a bulk operation task to remove agents from a group. +version_added: "0.0.1" +description: + - This module creates a bulk operation task to remove agents from a group. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module was made with https://developer.tenable.com/reference/bulk-remove-agents docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group + - valkiriaaquatica.tenable.bulk +""" + +EXAMPLES = r""" +- name: Remove agents to a group filtering + remove_agents_from_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "123456789" + criteria: + filters: ["name:match:laptop"] + all_agents: true + wildcard: "wildcard" + filter_type: "and" + hardcoded_filters: ["name:match:office"] + items: ["12345", "65789"] + not_items: ["98765"] + +- name: Remove agents to a group using enviroment creds and no filters + remove_agents_from_group: + group_id: "123456789" + items: ["12345", "65789"] +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: ID of the created task. + type: str + container_uuid: + description: UUID of the container. + type: str + status: + description: Status of the task. + type: str + message: + description: Message about the task status. + type: str + start_time: + description: Start time of the task. + type: int + last_update_time: + description: Last update time of the task. + type: int + total_work_units: + description: Total work units of the task. + type: int + total_work_units_completed: + description: Total work units completed. + type: int + completion_percentage: + description: Completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "criteria", "group_id", "items", "not_items") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agent-groups/{module.params['group_id']}/agents/_bulk/remove" + + payload_keys = ["criteria", "items", "not_items"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/remove_agents_from_network.py b/plugins/modules/remove_agents_from_network.py new file mode 100644 index 0000000..9eddda6 --- /dev/null +++ b/plugins/modules/remove_agents_from_network.py @@ -0,0 +1,110 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: remove_agents_from_network +short_description: Creates a bulk operation task to remove agents from a group. +version_added: "0.0.1" +description: + - This module creates a bulk operation task to remove agents from a group. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module was made with https://developer.tenable.com/reference/bulk-remove-agents docs. +options: + network_uuid: + description: + - The UUID of the network object you want to update. + - You cannot update the default network object. + - To list all networks and get the ID use the list_networks moduke. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.bulk +""" + +EXAMPLES = r""" +- name: Remove agents to a group filtering + remove_agents_from_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_uuid: "123456789" + criteria: + filters: ["name:match:laptop"] + all_agents: true + wildcard: "wildcard" + filter_type: "and" + hardcoded_filters: ["name:match:office"] + items: ["12345", "65789"] + not_items: ["98765"] + +- name: Remove agents to a group using enviroment creds and no filters + remove_agents_from_network: + network_uuid: "123456789" + items: ["12345", "65789"] +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: ID of the created task. + type: str + container_uuid: + description: UUID of the container. + type: str + status: + description: Status of the task. + type: str + message: + description: Message about the task status. + type: str + start_time: + description: Start time of the task. + type: int + last_update_time: + description: Last update time of the task. + type: int + total_work_units: + description: Total work units of the task. + type: int + total_work_units_completed: + description: Total work units completed. + type: int + completion_percentage: + description: Completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "criteria", "network_uuid", "items", "not_items") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scanners/null/agents/_bulk/removeFromNetwork" + + payload_keys = ["network_uuid", "criteria", "items", "not_items"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/remove_scanner_from_scanner_group.py b/plugins/modules/remove_scanner_from_scanner_group.py new file mode 100644 index 0000000..2613e5e --- /dev/null +++ b/plugins/modules/remove_scanner_from_scanner_group.py @@ -0,0 +1,60 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: remove_scanner_from_scanner_group +short_description: Remove a scanner from the scanner group. +version_added: "0.0.1" +description: + - This module removes a scanner from the scanner group. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/scanner-groups-delete-scanner docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Remove scanner from scanner group + remove_scanner_from_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 12345 + scanner_id: 67890 + +- name: Remove scanner from scanner group using enviroment creds + remove_scanner_from_scanner_group: + group_id: 12345 + scanner_id: 67890 +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "scanner_id") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanner-groups/{module.params['group_id']}/scanners/{module.params['scanner_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/rename_agent.py b/plugins/modules/rename_agent.py new file mode 100644 index 0000000..5c00d3f --- /dev/null +++ b/plugins/modules/rename_agent.py @@ -0,0 +1,279 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: rename_agent +short_description: Renames an agent. +version_added: "0.0.1" +description: + - This module fetches renames an agent. + - The module is made from https://developer.tenable.com/reference/io-agents-rename docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.agent + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Rename agent using enviroment creds + rename_agent: + agent_id: "123456789" + name: "new_name" + +- name: Rename agent using creds in vars + rename_agent: + access_key: "your_access_key" + secret_key: "your_secret_key" + agent_id: "123456789" + name: "new_name" +""" + +RETURN = r""" +api_response: + description: Detailed information about the agent. + type: dict + returned: always + contains: + data: + description: Contains all relevant details about the agent. + type: dict + contains: + agent_uuid: + description: UUID of the agent. + type: str + returned: always + sample: "1233456789" + aws_account_id: + description: AWS account ID associated with the agent. + type: str + returned: always + sample: "1233456789" + aws_instance_id: + description: AWS instance ID of the agent. + type: str + returned: always + sample: "i-1233456789" + container_uuid: + description: UUID of the container in which the agent is located. + type: str + returned: always + sample: "1233456789" + created: + description: UNIX timestamp of when the agent was created. + type: int + returned: always + sample: 1233456789 + created_in_seconds: + description: Creation time in seconds. + type: int + returned: always + sample: 1233456789 + distro: + description: Distribution information of the agent's operating system. + type: str + returned: always + sample: "1233456789" + engine_version: + description: Version of the scanning engine used on the agent. + type: str + returned: always + sample: "1.1.1" + health_events: + description: List of health events associated with the agent. + type: list + returned: always + sample: [] + id: + description: Unique identifier of the agent. + type: int + returned: always + sample: 1233456789 + ip: + description: IP address of the agent. + type: str + returned: always + sample: "192.168.1.0" + last_connect: + description: UNIX timestamp of the last time the agent connected. + type: int + returned: always + sample: 1233456789 + last_connect_in_seconds: + description: Last connection time in seconds. + type: int + returned: always + sample: 1233456789 + last_scanned: + description: UNIX timestamp of the last time the agent was scanned. + type: int + returned: always + sample: 1233456789 + last_scanned_in_seconds: + description: Last scanned time in seconds. + type: int + returned: always + sample: 1233456789 + loaded_plugin_set: + description: The set of plugins loaded during the last scan. + type: str + returned: always + sample: "1233456789" + mac_addrs: + description: MAC addresses associated with the agent. + type: str + returned: always + sample: "macs" + modified: + description: UNIX timestamp of when the agent was last modified. + type: int + returned: always + sample: 1233456789 + modified_in_seconds: + description: Last modified time in seconds. + type: int + returned: always + sample: 1233456789 + name: + description: Name of the agent. + type: str + returned: always + sample: "name" + network_uuid: + description: UUID of the network to which the agent belongs. + type: str + returned: always + sample: "1233456789" + owner_uuid: + description: UUID of the owner of the agent. + type: str + returned: always + sample: "1233456789" + platform: + description: Operating system platform of the agent. + type: str + returned: always + sample: "LINUX" + remote_settings: + description: Settings configured remotely for the agent. + type: list + elements: dict + contains: + allowable_values: + description: List of allowable values for a setting. + type: list + elements: dict + contains: + value: + description: An allowable value for the setting. + type: str + sample: "high" + default: + description: Default value for the setting. + type: str + sample: "high" + description: + description: Description of the setting. + type: str + sample: "description" + name: + description: Name of the setting. + type: str + sample: "Sname" + service_restart: + description: Indicates if a service restart is needed after changing the setting. + type: bool + sample: true + setting: + description: Specific setting being described. + type: str + sample: "setting" + status: + description: Status of the setting. + type: str + sample: "status" + type: + description: Type of the setting. + type: str + sample: "type" + value: + description: Current value of the setting. + type: str + sample: "value" + restart_pending: + description: Indicates if a restart is pending for the agent. + type: bool + returned: always + sample: false + status: + description: Current status of the agent. + type: str + returned: always + sample: "off" + supports_remote_logs: + description: Indicates if the agent supports remote logging. + type: bool + returned: always + sample: false + supports_remote_settings: + description: Indicates if the agent supports remote settings management. + type: bool + returned: always + sample: true + tracking_id: + description: Tracking identifier for the agent. + type: str + returned: always + sample: "tracking_id" + ui_build: + description: Build version of the user interface on the agent. + type: str + returned: always + sample: "12" + ui_version: + description: Version of the user interface on the agent. + type: str + returned: always + sample: "ui_version" + uuid: + description: UUID of the agent. + type: str + returned: always + sample: "uuid" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "agent_id", "name") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agents/{module.params['agent_id']}" + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PATCH", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/rename_folder.py b/plugins/modules/rename_folder.py new file mode 100644 index 0000000..4414013 --- /dev/null +++ b/plugins/modules/rename_folder.py @@ -0,0 +1,85 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: rename_folder +short_description: Renames a folder for the current user. +version_added: "0.0.1" +description: + - This module renames a folder for the current user. + - You cannot rename Tenable-provided scan folders or custom folder that belong to other users (even if your account has administrator privileges). + - The module is made from https://developer.tenable.com/reference/folders-edit docs. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. +options: + folder_id: + description: + - The ID of the folder to rename. + required: true + type: int +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Rename agent using enviroment creds + rename_agent: + agent_id: "123456789" + name: "new_name" + +- name: Rename agent using creds in vars + rename_agent: + access_key: "your_access_key" + secret_key: "your_secret_key" + agent_id: "123456789" + name: "new_name" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Data content of the response from the Tenable API. + type: dict + returned: always + sample: {} + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "folder_id", "name") + argument_spec["folder_id"]["required"] = True + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"folders/{module.params['folder_id']}" + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/resume_scan.py b/plugins/modules/resume_scan.py new file mode 100644 index 0000000..719bb34 --- /dev/null +++ b/plugins/modules/resume_scan.py @@ -0,0 +1,59 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: resume_scan +short_description: Resumes a scan. +version_added: "0.0.1" +description: + - This module resumes a scan. + - You can only resume a scan that has a status of paused. + - The module is made from https://developer.tenable.com/reference/scans-resume docs. + - Requires SCAN OPERATOR [24] and CAN EXECUTE [32] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Resumes a scan using enviroment creds + resume_scan: + scan_id: "123456" + +- name: Resumes a scan + resume_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scan_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/resume" + + run_module(module, endpoint, method="POST") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/send_instructions_to_agents.py b/plugins/modules/send_instructions_to_agents.py new file mode 100644 index 0000000..282511c --- /dev/null +++ b/plugins/modules/send_instructions_to_agents.py @@ -0,0 +1,126 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: send_instructions_to_agents +short_description: Create instructions for agents to perform. +version_added: "0.0.1" +description: + - This module creates instructions for agents to perform, including restarting or changing local product settings. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module es hecho de https://developer.tenable.com/reference/io-agent-bulk-operations-directive docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.bulk + - valkiriaaquatica.tenable.directive +""" + +EXAMPLES = r""" +- name: Send restart instructions to agents + send_instructions_to_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + criteria: + all_agents: true + wildcard: "wildcard" + filters: ["core_version:lt:10.0.0"] + filter_type: "and" + hardcoded_filters: ["hardcoded_filters"] + directive: + type: "restart" + options: + hard: true + idle: false + items: + - "12345" + +- name: Send settings instructions to agents using environment creds + send_instructions_to_agents: + criteria: + filters: ["core_version:lt:10.0.0"] + filter_type: "and" + hardcoded_filters: ["hardcoded_filters"] + directive: + type: "settings" + options: + settings: + - setting: "backend_log_level" + value: "debug" + - setting: "auto_update" + value: "enabled" + items: + - "12345" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: The unique identifier for the task. + type: str + container_uuid: + description: The UUID of the container. + type: str + status: + description: The current status of the task. + type: str + message: + description: A message describing the current state of the task. + type: str + start_time: + description: The start time of the task in epoch milliseconds. + type: int + last_update_time: + description: The last update time of the task in epoch milliseconds. + type: int + total_work_units: + description: The total number of work units for the task. + type: int + total_work_units_completed: + description: The number of work units completed for the task. + type: int + completion_percentage: + description: The completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "criteria", "items", "not_items", "directive") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scanners/null/agents/_bulk/directive" + + payload_keys = ["criteria", "items", "not_items", "directive"] + payload = build_payload(module, payload_keys) + + directive_type = module.params["directive"]["type"] + if directive_type == "restart": + if "settings" in module.params["directive"]["options"]: + module.fail_json(msg="Cannot use 'settings' options with 'restart' directive type") + elif directive_type == "settings": + if "hard" in module.params["directive"]["options"] or "idle" in module.params["directive"]["options"]: + module.fail_json(msg="Cannot use 'hard' or 'idle' options with 'settings' directive type") + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/send_instructions_to_agents_group.py b/plugins/modules/send_instructions_to_agents_group.py new file mode 100644 index 0000000..3c18ab8 --- /dev/null +++ b/plugins/modules/send_instructions_to_agents_group.py @@ -0,0 +1,132 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: send_instructions_to_agents_group +short_description: Creates a bulk operation task to add agents to a group. +version_added: "0.0.1" +description: + - This module creates a bulk operation task to add agents to a group. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module was made with https://developer.tenable.com/reference/bulk-add-agents docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group + - valkiriaaquatica.tenable.bulk + - valkiriaaquatica.tenable.directive +""" + +EXAMPLES = r""" +- name: Send restart instructions to agent group + send_instructions_to_agents_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "123456" + criteria: + all_agents: true + wildcard: "wildcard" + filters: ["core_version:lt:10.0.0"] + filter_type: "and" + hardcoded_filters: ["hardcoded_filters"] + directive: + type: "restart" + options: + hard: true + idle: false + items: + - "12345" + +- name: Send settings instructions to agents group using environment creds + send_instructions_to_agents_group: + group_id: "123456" + criteria: + filters: ["core_version:lt:10.0.0"] + filter_type: "and" + hardcoded_filters: ["hardcoded_filters"] + directive: + type: "settings" + options: + settings: + - setting: "backend_log_level" + value: "debug" + - setting: "auto_update" + value: "enabled" + items: + - "12345" +""" + + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: The unique identifier for the task. + type: str + container_uuid: + description: The UUID of the container. + type: str + status: + description: The current status of the task. + type: str + message: + description: A message describing the current state of the task. + type: str + start_time: + description: The start time of the task in epoch milliseconds. + type: int + last_update_time: + description: The last update time of the task in epoch milliseconds. + type: int + total_work_units: + description: The total number of work units for the task. + type: int + total_work_units_completed: + description: The number of work units completed for the task. + type: int + completion_percentage: + description: The completion percentage of the task. + type: int +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "criteria", "group_id", "items", "not_items", "directive") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/null/agent-groups/{module.params['group_id']}/agents/_bulk/directive" + + payload_keys = ["criteria", "items", "not_items", "directive"] + payload = build_payload(module, payload_keys) + + directive_type = module.params["directive"]["type"] + if directive_type == "restart": + if "settings" in module.params["directive"]["options"]: + module.fail_json(msg="Cannot use 'settings' options with 'restart' directive type") + elif directive_type == "settings": + if "hard" in module.params["directive"]["options"] or "idle" in module.params["directive"]["options"]: + module.fail_json(msg="Cannot use 'hard' or 'idle' options with 'settings' directive type") + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/send_instructions_to_multiple_scanners.py b/plugins/modules/send_instructions_to_multiple_scanners.py new file mode 100644 index 0000000..de0a368 --- /dev/null +++ b/plugins/modules/send_instructions_to_multiple_scanners.py @@ -0,0 +1,226 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: send_instructions_to_multiple_scanners +short_description: Create instructions for multiple scanners to perform. +version_added: "0.0.1" +description: + - This module sends instructions to multiple specified scanners. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/io-scanners-directive-bulk docs. +options: + all_scanners: + description: + - Indicates whether or not to send the directives to all scanners. + type: bool + required: false + scanners: + description: + - An array of scanner UUIDs to send the directives to. + type: list + elements: str + required: false + directive: + description: + - Specifies the instructions you wish to send to the scanners. + type: dict + required: true + suboptions: + type: + description: + - The type of instruction to perform. + - To restart the scanner, choose restart. + - To modify settings, choose settings. + type: str + required: true + choices: ["restart", "settings"] + restart: + description: + - Options for the restart operation. + type: dict + required: false + suboptions: + hard: + description: + - Indicates whether or not to perform a hard restart. + type: bool + required: false + idle: + description: + - Indicates whether or not to restart when the scanning engine is idle. + type: bool + required: false + settings: + description: + - The settings to change. + type: list + elements: dict + required: false + suboptions: + setting: + description: + - The setting to send to the scanner. Supported settings are backend_log_level, auto_update, and auto_update_delay. + - For information about these settings and a list of possible values to use with the settings in Tenable docs. + type: str + required: true + choices: ["backend_log_level", "auto_update", "auto_update_delay"] + value: + description: + - The value to use for the specified setting. + - Choose either a single string value or a single integer value to use for the value. + type: raw + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Send restart instructions to multiple scanners + send_instructions_to_multiple_scanners: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + all_scanners: true + directive: + type: "restart" + restart: + hard: true + idle: false + +- name: Send settings instructions to multiple scanners using enviroment creds + send_instructions_to_multiple_scanners: + scanners: + - "123e4567-e89b-12d3-a456-426614174000" + - "123e4567-e89b-12d3-a456-426614174001" + directive: + type: "settings" + settings: + - setting: "backend_log_level" + value: "debug" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + container_uuid: + description: UUID of the container. + type: str + uuid: + description: UUID of the directive. + type: str + sensor_uuid: + description: UUID of the sensor. + type: str + owner_uuid: + description: UUID of the owner. + type: str + type: + description: Type of the directive. + type: str + status: + description: Status of the directive. + type: str + created: + description: Creation timestamp. + type: int + modified: + description: Modification timestamp. + type: int + results_blob_size: + description: Size of the results blob. + type: int + options: + description: Options for the directive. + type: dict + contains: + settings: + description: List of settings. + type: list + elements: dict + contains: + setting: + description: The setting to change. + type: str + value: + description: The value for the setting. + type: raw + ttl: + description: Time to live for the directive. + type: int + id: + description: ID of the directive. + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "all_scanners": {"required": False, "type": "bool"}, + "scanners": {"required": False, "type": "list", "elements": "str"}, + "directive": { + "required": True, + "type": "dict", + "options": { + "type": {"required": True, "type": "str", "choices": ["restart", "settings"]}, + "restart": { + "required": False, + "type": "dict", + "options": { + "hard": {"required": False, "type": "bool"}, + "idle": {"required": False, "type": "bool"}, + }, + }, + "settings": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "setting": { + "required": True, + "type": "str", + "choices": ["backend_log_level", "auto_update", "auto_update_delay"], + }, + "value": {"required": True, "type": "raw"}, + }, + }, + }, + }, + } + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + directive = module.params["directive"] + if directive["type"] == "restart" and directive.get("settings"): + module.fail_json(msg="You cannot provide both 'restart' and 'settings' options") + if directive["type"] == "settings" and directive.get("restart"): + module.fail_json(msg="You cannot provide both 'restart' and 'settings' options") + + endpoint = "scanners/directive" + payload_keys = ["all_scanners", "scanners", "directive"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/send_instructions_to_scanner.py b/plugins/modules/send_instructions_to_scanner.py new file mode 100644 index 0000000..db2366b --- /dev/null +++ b/plugins/modules/send_instructions_to_scanner.py @@ -0,0 +1,203 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: send_instructions_to_scanner +short_description: Create instructions for a single scanner to perform. +version_added: "0.0.1" +description: + - This module sends instructions to a specified scanner. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/io-scanners-directive docs. +options: + scanner_uuid: + description: + - The UUID of the scanner. + type: str + required: true + type: + description: + - The type of instruction to perform. + - To restart the scanner, choose restart. + - To modify settings, choose settings. + type: str + required: true + choices: ["restart", "settings"] + restart: + description: + - Options for the restart operation. + type: dict + required: false + suboptions: + hard: + description: + - Indicates whether or not to perform a hard restart. + - A hard restart will inform the scanner to restart immediately regardless of what it is doing. + type: bool + required: false + idle: + description: + - Indicates whether or not to restart when the scanning engine is idle. + type: bool + required: false + settings: + description: + - The settings to change. + type: list + elements: dict + required: false + suboptions: + setting: + description: + - The setting to send to the scanner. + - Supported settings are backend_log_level, auto_update, and auto_update_delay + - For information about these settings and a list of possible values to use with the settings in Tenable docs. + type: str + required: false + choices: ["backend_log_level", "auto_update", "auto_update_delay"] + value: + description: + - The value to use for the specified setting. + - Choose either a single string value or a single integer value to use for the value. + type: raw + required: false +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Send restart instructions to a scanner + send_instructions_to_scanner: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scanner_uuid: "123e4567-e89b-12d3-a456-426614174000" + type: "restart" + restart: + hard: true + idle: false + +- name: Send settings instructions to a scanner using enviroment creds + send_instructions_to_scanner: + scanner_uuid: "123e4567-e89b-12d3-a456-426614174000" + type: "settings" + settings: + - setting: "backend_log_level" + value: "debug" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + container_uuid: + description: UUID of the container. + type: str + uuid: + description: UUID of the directive. + type: str + sensor_uuid: + description: UUID of the sensor. + type: str + owner_uuid: + description: UUID of the owner. + type: str + type: + description: Type of the directive. + type: str + status: + description: Status of the directive. + type: str + created: + description: Creation timestamp. + type: int + modified: + description: Modification timestamp. + type: int + results_blob_size: + description: Size of the results blob. + type: int + options: + description: Options for the directive. + type: dict + contains: + settings: + description: List of settings. + type: list + elements: dict + contains: + setting: + description: The setting to change. + type: str + value: + description: The value for the setting. + type: raw + ttl: + description: Time to live for the directive. + type: int + id: + description: ID of the directive. + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + specific_spec = { + "scanner_uuid": {"required": True, "type": "str"}, + "type": {"required": True, "type": "str", "choices": ["restart", "settings"]}, + "restart": { + "required": False, + "type": "dict", + "options": {"hard": {"required": False, "type": "bool"}, "idle": {"required": False, "type": "bool"}}, + }, + "settings": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "setting": { + "required": False, + "type": "str", + "choices": ["backend_log_level", "auto_update", "auto_update_delay"], + }, + "value": {"required": False, "type": "raw"}, + }, + }, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + payload_keys = ["type", "restart", "settings"] + payload = build_payload(module, payload_keys) + + if module.params["type"] == "restart" and module.params.get("settings"): + module.fail_json(msg="You cannot provide both 'restart' and 'settings' options") + if module.params["type"] == "settings" and module.params.get("restart"): + module.fail_json(msg="You cannot provide both 'restart' and 'settings' options") + + scanner_uuid = module.params["scanner_uuid"] + endpoint = f"scanners/{scanner_uuid}/directive" + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/stop_scan.py b/plugins/modules/stop_scan.py new file mode 100644 index 0000000..eca6da6 --- /dev/null +++ b/plugins/modules/stop_scan.py @@ -0,0 +1,69 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: stop_scan +short_description: Stops a scan. +version_added: "0.0.1" +description: + - This module stops a scan. + - You can only stop a scan that has a status of pending, running, or resuming + - To stop a scan with a status of stopping or publishing, use the forc_stop_scan module. + - The module is made from https://developer.tenable.com/reference/scans-stop docs. + - Requires SCAN OPERATOR [24] and CAN EXECUTE [32] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Create folder using enviroment creds + stop_scan: + scan_id: "123456" + +- name: Create folder passing creds as vars + stop_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" +""" + + +RETURN = r""" +api_response: + description: Response returned by the Tenable api. + returned: always when a request is made, independent if it correct or incorrect. + type: complex + contains: + status_code: + description: The HTTP status code returned by the API if an error occurred. + type: int +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "scan_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}/stop" + + run_module(module, endpoint, method="POST") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/toggle_scanner_link_state.py b/plugins/modules/toggle_scanner_link_state.py new file mode 100644 index 0000000..aa5df4c --- /dev/null +++ b/plugins/modules/toggle_scanner_link_state.py @@ -0,0 +1,72 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: toggle_scanner_link_state +short_description: Enables or disables the link state of the scanner identified by scanner_id. +version_added: "0.0.1" +description: + - This module enables or disables the link state of the scanner identified by scanner_id. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/scanners-toggle-link-state docs. +options: + link: + description: + - Pass 1 to enable the link. Pass 0 to disable. + type: int + required: true + choices: [0, 1] +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: Enable the link state of a scanner + toggle_scanner_link_state: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scanner_id: 123456 + link: 1 + +- name: Disable the link state of a scanner using environment credentials + toggle_scanner_link_state: + scanner_id: 123456 + link: 0 +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scanner_id") + specific_spec = {"link": {"required": True, "type": "int", "choices": [0, 1]}} + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/{module.params['scanner_id']}/link" + + payload_keys = ["link"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/unlink_agent.py b/plugins/modules/unlink_agent.py new file mode 100644 index 0000000..08c769d --- /dev/null +++ b/plugins/modules/unlink_agent.py @@ -0,0 +1,59 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: unlink_agent +short_description: Unlinks an agent. +version_added: "0.0.1" +description: + - This module unlinks an agent. + - The module is made from https://developer.tenable.com/reference/agents-delete docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.agent +""" + +EXAMPLES = r""" +- name: Get information of a agent + unlink_agent: + access_key: "your_access_key" + secret_key: "your_secret_key" + agent_id: 11111 + +- name: Get information of a agent using enviroment keys + unlink_agent: + agent_id: 11111 +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "agent_id") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scanners/null/agents/{module.params['agent_id']}" + + run_module(module, endpoint, method="DELETE") + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/unlink_agent_linux.py b/plugins/modules/unlink_agent_linux.py new file mode 100644 index 0000000..9707ca9 --- /dev/null +++ b/plugins/modules/unlink_agent_linux.py @@ -0,0 +1,69 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: unlink_agent_linux +short_description: Unlink nessus agent from Linux server. +version_added: "0.0.1" +description: + - This module unlinks the nessus agent relation that has a server with Tenable IO. + - No macOS module yet. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +""" + + +EXAMPLES = r""" +- name: Unlink the Nessus Agent on the target machine + unlink_agent_linux: + become: true +""" + +RETURN = r""" +msg: + description: Message about the result of the unlink operation. + returned: on success + type: str + sample: "Nessus Agent unlinked successfully" +""" + + +import subprocess + +from ansible.module_utils.basic import AnsibleModule + + +def unlink_nessus_agent(module): + command = "/opt/nessus_agent/sbin/nessuscli agent unlink" + + try: + subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT) + return True, "Nessus Agent unlinked successfully" + except subprocess.CalledProcessError as e: + output = e.output.decode() + if "No host information found" in output: + return True, "No Agent is running on the server, no need to unlink" + else: + module.fail_json(msg=f"Failed to unlink Nessus Agent: {output}") + + +def main(): + module = AnsibleModule( + argument_spec=dict(), + supports_check_mode=False, + ) + + is_unlinked, message = unlink_nessus_agent(module) + if is_unlinked: + module.exit_json(changed=True, msg=message) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/unlink_agent_windows.ps1 b/plugins/modules/unlink_agent_windows.ps1 new file mode 100644 index 0000000..9764454 --- /dev/null +++ b/plugins/modules/unlink_agent_windows.ps1 @@ -0,0 +1,58 @@ +#!powershell +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic +# Tested on PowerShell v5.1 and higher on Windows Server 2016 and higher; + +#Requires -Module Ansible.ModuleUtils.Legacy + +$spec = @{ + supports_check_mode = $false +} + +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + +$nessuscliPath = 'C:\Program Files\Tenable\Nessus Agent\nessuscli.exe' +$arguments = "agent unlink" + +try { + $processInfo = New-Object System.Diagnostics.ProcessStartInfo + $processInfo.FileName = $nessuscliPath + $processInfo.RedirectStandardError = $true + $processInfo.RedirectStandardOutput = $true + $processInfo.UseShellExecute = $false + $processInfo.Arguments = $arguments + $processInfo.CreateNoWindow = $true + + $process = New-Object System.Diagnostics.Process + $process.StartInfo = $processInfo + $process.Start() | Out-Null + $process.WaitForExit() + + $stdout = $process.StandardOutput.ReadToEnd() + $stderr = $process.StandardError.ReadToEnd() + + $module.Result['command_output'] = $stdout + $module.Result['command_error'] = $stderr + $module.Result['command'] = $arguments + + if ($stdout -like "*is not linked*") { + $module.Result['changed'] = $false + $module.Result['msg'] = "The agent is not linked, no changes are required." + } + elseif ($stdout -like "*Successfully unlinked*") { + $module.Result['changed'] = $true + $module.Result['msg'] = "Agent unlinked successfully." + } + else { + $module.Result['changed'] = $false + $module.FailJson("An unexpected error occurred during the unlink operation: $stdout") + } +} +catch { + $module.FailJson("Error unlinking the agent from Tenable: $_") +} + +$module.ExitJson() + diff --git a/plugins/modules/unlink_agent_windows.py b/plugins/modules/unlink_agent_windows.py new file mode 100644 index 0000000..9f6d353 --- /dev/null +++ b/plugins/modules/unlink_agent_windows.py @@ -0,0 +1,40 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: unlink_agent_windows +short_description: Unlinks a Tenable.IO agent from a Windows machine. +version_added: "0.0.1" +description: + - Unlinks a Tenable.IO agent from a Windows server. + - This module utilizes PowerShell to execute the Tenable Nessus Agent 'nessuscli.exe' command to unlink an agent. + - Requires PowerShell v5.1 or higher on Windows Server 2016 and higher. +options: {} +notes: + - This module does not support check mode. + - Nessuscli.exe path of the module is the default C:\Program Files\Tenable\Nessus Agent\nessuscli.exe + - Ensure that the 'nessuscli.exe' path is correct and accessible on the system where this module is run. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +""" + +EXAMPLES = r""" +- name: Unlink Nessus Agent from Tenable.io + unlink_agent_windows: +""" + +RETURN = r""" +msg: + description: The output message of the unlink command. + type: str + returned: always + sample: "Agent unlinked successfully." +""" diff --git a/plugins/modules/unlink_agents.py b/plugins/modules/unlink_agents.py new file mode 100644 index 0000000..ce4a212 --- /dev/null +++ b/plugins/modules/unlink_agents.py @@ -0,0 +1,99 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: unlink_agents +short_description: Creates a bulk operation task to unlink agents. +version_added: "0.0.1" +description: + - This module creates a bulk operation task to unlink agents. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module was made with https://developer.tenable.com/reference/bulk-unlink-agents docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.bulk +""" + +EXAMPLES = r""" +- name: Unlink agents filtering + unlink_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + criteria: + filters: ["name:match:laptop"] + all_agents: true + wildcard: "wildcard" + filter_type: "and" + hardcoded_filters: ["name:match:office"] + items: ["12345", "65789"] + not_items: ["98765"] + +- name: Unlink agents using enviroment creds and no filters + unlink_agents: + items: ["12345", "65789"] +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + task_id: + description: ID of the created task. + type: str + container_uuid: + description: UUID of the container. + type: str + status: + description: Status of the task. + type: str + message: + description: Message about the task status. + type: str + start_time: + description: Start time of the task. + type: int + last_update_time: + description: Last update time of the task. + type: int + total_work_units: + description: Total work units of the task. + type: int + total_work_units_completed: + description: Total work units completed. + type: int + completion_percentage: + description: Completion percentage of the task. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "criteria", "items", "not_items") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = "scanners/null/agents/_bulk/unlink" + + payload_keys = ["criteria", "items", "not_items"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_agent_configuration.py b/plugins/modules/update_agent_configuration.py new file mode 100644 index 0000000..f5f346d --- /dev/null +++ b/plugins/modules/update_agent_configuration.py @@ -0,0 +1,137 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: update_agent_configuration +short_description: Updates the configuration of agents associated with a specific scanner. +version_added: "0.0.1" +description: + - This module updates the configuration of agents associated with a specific scanner. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/agent-config-edit docs. +options: + auto_unlink: + description: + - The auto unlink configuration. + required: false + type: dict + suboptions: + enabled: + description: + - If true, agent auto-unlink is enabled. + - Enabling auto-unlink causes it to take effect against all agents retroactively. + required: false + type: bool + expiration: + description: + - The expiration time for agents, in days. + required: false + type: int + software_update: + description: + - The expiration time for agents, in days. + - If an agent has not communicated in this number of days, it will be considered expired and auto-unlinked + if auto_unlink.enabled is true + - Valid values are 1-365. + required: false + type: bool +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: Update agent configuration + update_agent_configuration: + access_key: "your_access_key" + secret_key: "your_secret_key" + scanner_id: 12345 + auto_unlink: + enabled: true + expiration: 180 + software_update: true + +- name: Update agent configuration using enviroment creds + update_agent_configuration: + scanner_id: 12345 + auto_unlink: + enabled: true + expiration: 180 + software_update: true + tags: update_agent_config +""" + +RETURN = r""" +api_response: + description: Contains the raw response from the Tenable API. + returned: success + type: dict + contains: + auto_unlink: + description: Auto unlink settings. + returned: success + type: dict + contains: + enabled: + description: Indicates if auto unlink is enabled. + type: bool + sample: true + expiration: + description: The expiration period for auto unlink. + type: int + sample: 180 + software_update: + description: Indicates if software update is enabled. + returned: success + type: bool + sample: true + hybrid_scanning_enabled: + description: Indicates if hybrid scanning is enabled. + returned: success + type: bool + sample: false +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scanner_id") + specific_spec = { + "auto_unlink": { # unique for this module + "required": False, + "type": "dict", + "options": { + "enabled": {"required": False, "type": "bool"}, + "expiration": {"required": False, "type": "int"}, + }, + }, + "software_update": {"required": False, "type": "bool"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/{module.params['scanner_id']}/agents/config" + + payload_keys = ["software_update", "auto_unlink"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_agent_exclusion.py b/plugins/modules/update_agent_exclusion.py new file mode 100644 index 0000000..9303e78 --- /dev/null +++ b/plugins/modules/update_agent_exclusion.py @@ -0,0 +1,157 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: update_agent_exclusion +short_description: Updates an agent exclusion. +version_added: "0.0.1" +description: + - This module updates an agent exclusion. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/scanners-32-agents-exclusions docs. +options: + name: + description: + - The name of the exclusion. + type: str + required: false + description: + description: + - The description of the exclusion. + type: str + required: false +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.exclusion + - valkiriaaquatica.tenable.schedule +""" + +EXAMPLES = r""" +- name: Update agent exclusion + update_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: 124234 + name: "Updated exclusion name" + description: "Updated description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-07-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + tags: update_agent_exclusion +""" + +RETURN = r""" +changed: + description: Indicates if the task resulted in any changes. + returned: always + type: bool + sample: true +api_response: + description: Contains the raw response from the Tenable API. + returned: success + type: dict + contains: + id: + description: The ID of the updated exclusion. + type: int + sample: 124234 + name: + description: The name of the updated exclusion. + type: str + sample: "Updated exclusion name" + description: + description: The description of the updated exclusion. + type: str + sample: "Updated description" + creation_date: + description: The date when the exclusion was created. + type: int + sample: 1543541807 + last_modification_date: + description: The date when the exclusion was last modified. + type: int + sample: 1543541807 + schedule: + description: Schedule details for the exclusion. + type: dict + contains: + endtime: + description: The end time of the schedule. + type: str + sample: "2019-12-31 19:35:00" + enabled: + description: Indicates if the schedule is enabled. + type: bool + sample: true + rrules: + description: Recurrence rules for the schedule. + type: dict + contains: + freq: + description: Frequency of the recurrence. + type: str + sample: "ONETIME" + interval: + description: Interval for the recurrence. + type: int + sample: 1 + byweekday: + description: Days of the week for the recurrence. + type: str + sample: "SU" + bymonthday: + description: Days of the month for the recurrence. + type: int + sample: 1 + timezone: + description: Timezone for the schedule. + type: str + sample: "US/Pacific" + starttime: + description: The start time of the schedule. + type: str + sample: "2024-06-01 00:00:00" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "schedule", "description", "exclusion_id") + specific_spec = { + "name": {"required": False, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + exclusion_id = module.params["exclusion_id"] + endpoint = f"scanners/null/agents/exclusions/{exclusion_id}" + + payload_keys = ["name", "description", "schedule"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_agent_group_name.py b/plugins/modules/update_agent_group_name.py new file mode 100644 index 0000000..2e74d74 --- /dev/null +++ b/plugins/modules/update_agent_group_name.py @@ -0,0 +1,92 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: update_agent_group_name +short_description: Changes the name of the agent group. +version_added: "0.0.1" +description: + - This module changes the name of the agent group. + - This module is made from https://developer.tenable.com/reference/agent-groups-configure docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group + - valkiriaaquatica.tenable.name +""" + + +EXAMPLES = r""" +- name: Update the name of an agent with enviroment creds + update_agent_group_name: + group_id: 123456 + name: new_name + +- name: Update the name of an agent passing credentials + update_agent_group_name: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 123456 + name: new_name +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + returned: on sucess. + type: dict + contains: + data: + description: Contains various details about the entity, such as agent count, creation and modification details, and ownership. + type: dict + returned: always + sample: + agents_count: 1 + container_uuid: "123456" + created: 123456 + created_in_seconds: 123456 + id: 166090 + modified: 123456 + modified_in_seconds: 123456 + name: "name" + owner_uuid: "123456" + shared: 1 + user_permissions: 1 + uuid: "123456" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "name") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + endpoint = f"scanners/null/agent-groups/{module.params['group_id']}" + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_an_exclusion.py b/plugins/modules/update_an_exclusion.py new file mode 100644 index 0000000..1a5b2cf --- /dev/null +++ b/plugins/modules/update_an_exclusion.py @@ -0,0 +1,198 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: update_an_exclusion +short_description: Updates an exclusion. +version_added: "0.0.1" +description: + - This module updates an exclusion. + - This module is made from https://developer.tenable.com/reference/exclusions-edit docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + name: + description: + - The name of the exclusion. + type: str + required: false + members: + description: + - The targets that you want excluded from scans. Specify multiple targets as a comma-separated string. + - Targets can be in the following formats IPv4 address, range of IPv4 addresses, CIDR notation, or a FQDN. + - Target example an individual IPv4 address 192.0.2.1 + - Target example a range of IPv4 addresses 192.0.2.1-192.0.2.255 + - Target example a CIDR notation 192.0.2.0/24 + - Target example a fully-qualified domain name FQDN host.domain.com + type: str + required: false + network_id: + description: + - The UUID of the network object you want to update. + - You cannot update the default network object. + - To list all networks and get the ID use the list_networks moduke. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.exclusion + - valkiriaaquatica.tenable.exclusion_params + - valkiriaaquatica.tenable.schedule +""" + +EXAMPLES = r""" +- name: Create a exclusion using enviroment creds + update_an_exclusion: + exclusion_id: 1 + name: "name" + description: "i am the exclusion" + members: "192.168.1.10" + schedule: + enabled: true + starttime: "2023-04-01 09:00:00" + endtime: "2023-04-01 17:00:00" + timezone: "America/New_York" + rrules: + freq: "WEEKLY" + interval: 1 + byweekday: "MO,TU,WE,TH,FR" + network_id: "123456" + +- name: Update a exclusion using variable creds + update_an_exclusion: + access_key: "your_access_key" + secret_key: "your_secret_key" + exclusion_id: 1 + name: "name" + members: "192.168.1.10,192.168.1.11" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains the data of the exclusion that was created or updated. + type: dict + contains: + creation_date: + description: UNIX timestamp when the exclusion was created. + type: int + returned: always + sample: 123456 + description: + description: Description of the exclusion. + type: str + returned: always + sample: "i am the exclusion" + id: + description: Unique identifier of the exclusion. + type: int + returned: always + sample: 123456 + last_modification_date: + description: UNIX timestamp when the exclusion was last modified. + type: int + returned: always + sample: 123456 + members: + description: IP addresses or other identifiers included in the exclusion. + type: str + returned: always + sample: "192.168.1.120" + name: + description: Name of the exclusion. + type: str + returned: always + sample: "exclusion" + network_id: + description: Network ID associated with the exclusion. + type: str + returned: always + sample: "123456" + schedule: + description: Schedule details for when the exclusion is active. + type: dict + returned: always + contains: + enabled: + description: Whether the schedule is enabled. + type: bool + sample: true + endtime: + description: End time of the exclusion schedule. + type: str + sample: "2023-04-01 17:00:00" + rrules: + description: Recurrence rules for the exclusion schedule. + type: dict + contains: + bymonthday: + description: Day of the month the exclusion recurs on (if applicable). + type: str + returned: when applicable + sample: null + byweekday: + description: Days of the week the exclusion recurs on. + type: str + sample: "MO,TU,WE,TH,FR" + freq: + description: Frequency of the recurrence. + type: str + sample: "WEEKLY" + interval: + description: Interval at which the recurrence repeats. + type: int + sample: 1 + starttime: + description: Start time of the exclusion schedule. + type: str + sample: "2023-04-01 09:00:00" + timezone: + description: Timezone of the exclusion schedule. + type: str + sample: "America/New_York" + status_code: + description: HTTP status code returned by the API. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "exclusion_id", "description", "schedule", "network_id") + specific_spec = { + "name": {"required": False, "type": "str"}, + "members": {"required": False, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + payload_keys = ["name", "members", "description", "schedule", "network_id"] + + payload = build_payload(module, payload_keys) + endpoint = f"exclusions/{module.params['exclusion_id']}" + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_attribute.py b/plugins/modules/update_attribute.py new file mode 100644 index 0000000..0b2ded1 --- /dev/null +++ b/plugins/modules/update_attribute.py @@ -0,0 +1,70 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: update_attribute +short_description: Updates the specified custom asset attribute. +version_added: "0.0.1" +description: + - This module Updates the specified custom asset attribute. + - Note You can only update non-key attributes like description. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/io-v3-asset-attributes-update docs. +options: + description: + description: + - The new or updated description for the custom asset attribute. + - Currently, description is the only non-primary key attribute that can be updated. + - If the name field needs a new value, then you should create a new custom asset attribute via the POST /api/v3/assets/attributes endpoint. + type: str + required: false +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.attributes +""" + +EXAMPLES = r""" +- name: Update the description of a custom asset attribute + update_attribute: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + attribute_id: "123" + description: "Updated description of the attribute" + +- name: Update the description of a attribute using enviroment creds + update_attribute: + attribute_id: "123" + description: "Updated description of the attribute" +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "attribute_id", "description") + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"api/v3/assets/attributes/{module.params['attribute_id']}" + payload_keys = ["description"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_group.py b/plugins/modules/update_group.py new file mode 100644 index 0000000..3c936e8 --- /dev/null +++ b/plugins/modules/update_group.py @@ -0,0 +1,87 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: update_group +short_description: Edit a group. +version_added: "0.0.1" +description: + - This module edits a group in Tenable.io. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. + - This module is made with https://developer.tenable.com/reference/groups-edit docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Update the name of a group + update_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 1233 + name: "New Group Name" + +- name: Update the name of a group using enviroment creds + update_group: + group_id: 1233 + name: "New Group Name" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + uuid: + description: The unique identifier for the group. + type: str + name: + description: The name of the group. + type: str + permissions: + description: The permissions of the group. + type: int + container_uuid: + description: The UUID of the container. + type: str + user_count: + description: The number of users in the group. + type: int + id: + description: The ID of the group. + type: int +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "name") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"groups/{module.params['group_id']}" + + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_managed_credential.py b/plugins/modules/update_managed_credential.py new file mode 100644 index 0000000..5fac4dc --- /dev/null +++ b/plugins/modules/update_managed_credential.py @@ -0,0 +1,208 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: update_managed_credential +short_description: Updates a managed credential object. +version_added: "0.0.1" +description: + - This module updates a managed credential object. + - Note You cannot use this endpoint to update the credential type. + - If you create a managed credential with the incorrect type, create a new managed credential with the + correct credential type, and delete the incorrect managed credential. + - Requires CAN EDIT [64] credential permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/credentials-update docs. +options: + name: + description: + - The name of the managed credential. + - This name must be unique within your Tenable Vulnerability Management instance. + type: str + required: false + description: + description: + - The description of the managed credential object. + type: str + required: false + ad_hoc: + description: + - A value specifying if the credential is managed (false) versus stored in a scan or policy configuration (true). + - You can only set this parameter from true to false + - You cannot set this parameter to true. If you omit this parameter, the value defaults to false. + type: bool + required: false + settings: + description: + - The configuration settings for the credential. + - The parameters of this object vary depending on the credential type. + - For more information, see https://developer.tenable.com/docs/determine-settings-for-credential-type . + - Note This form displays limited parameters that support a Windows type of credential that uses password authentication. + required: true + type: dict + suboptions: + domain: + description: + - The Windows domain to which the username belongs. + required: false + type: str + username: + description: + - The username on the target system. + required: false + type: str + auth_method: + description: + - The name for the authentication method. + - This value corresponds to the credentials[].types[].configuration[].options[].id attribute in the + response message of list_credential_types module. + required: false + type: str + password: + description: + - The user password on the target system. + required: false + type: str + permissions: + description: + - A list of user permissions for the managed credential. + - If a request message omits this parameter, Tenable Vulnerability Management automatically creates + a permissions object for the user account that submits the request. + required: false + type: list + elements: dict + suboptions: + grantee_uuid: + description: + - The UUID of the user or user group granted permissions for the managed credential. + - This parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential. + required: false + type: str + type: + description: + - A value specifying whether the grantee is a user (user) or a user group (group). + - This parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential. + required: false + type: str + permissions: + description: + - A value specifying the permissions granted to the user or user group for the credential. + - 32—The user can view credential information and use the credential in scans. Corresponds + to the Can Use permission in the user interface. + - 64—The user can view and edit credential settings, delete the credential, and use the credential in scans. + Corresponds to the Can Edit permission in the user interface. + - This parameter is required when assigning CAN USE (32) or CAN EDIT (64) permissions for a managed credential. + required: false + type: int + name: + description: + - The name of the user or user group that you want to grant permissions for the managed credential. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.credential +""" + +EXAMPLES = r""" +- name: Update managed credential + update_managed_credential: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + credential_uuid: "123456" + name: "name" + description: "description" + type: "type" + settings: + domain: "domain" + username: "username" + auth_method: "Password" + password: "password" + permissions: + - grantee_uuid: "grantee_uuid" + type: "user" + permissions: 64 + name: "name" + + +- name: Update managed credential using enviroment creds + update_managed_credential: + credential_uuid: "12345" + name: "name" + description: "description" + type: "type" + settings: + domain: "domain" + username: "username" + auth_method: "Password" + password: "password" + ad_hoc: true +""" + +RETURN = r""" +api_response: + description: Contains the raw response from the Tenable API. + returned: success + type: dict + contains: + updated: + description: Boolean indicating if the credentials was uploaded. + type: bool + sample: true +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "description", "credential_uuid") + specific_spec = { # unique arguments + "settings": { + "required": True, + "type": "dict", + "options": { + "domain": {"required": False, "type": "str"}, + "username": {"required": False, "type": "str"}, + "auth_method": {"required": False, "type": "str"}, + "password": {"required": False, "type": "str", "no_log": True}, + }, + }, + "permissions": { + "required": False, + "type": "list", + "elements": "dict", + "options": { + "grantee_uuid": {"required": False, "type": "str"}, + "type": {"required": False, "type": "str"}, + "permissions": {"required": False, "type": "int"}, + "name": {"required": False, "type": "str"}, + }, + }, + "ad_hoc": {"required": False, "type": "bool"}, + "name": {"required": False, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"credentials/{module.params['credential_uuid']}" + payload_keys = ["name", "description", "type", "settings", "permissions", "ad_hoc"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="POST", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_network.py b/plugins/modules/update_network.py new file mode 100644 index 0000000..8075e12 --- /dev/null +++ b/plugins/modules/update_network.py @@ -0,0 +1,152 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: update_network +short_description: Updates the name or description of a network object. +version_added: "0.0.1" +description: + - This module updates the name or description of a network object. + - The module is made from https://developer.tenable.com/reference/networks-update docs. + - Requires ADMINISTRATOR [64] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.network + - valkiriaaquatica.tenable.network_params +""" + + +EXAMPLES = r""" +- name: Update a network with enviroment creds + update_network: + network_id: "123456" + name: "new_name" + description: "i am the description" + assets_ttl_days: 60 + +- name: Update a network with creds as variables + update_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "123456" + name: "new_name" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response. + type: dict + returned: on success + contains: + data: + description: Contains all relevant details about the network or asset. + type: dict + contains: + assets_ttl_days: + description: The time-to-live (TTL) in days for assets within this network. + type: int + returned: always + sample: 1 + created: + description: UNIX timestamp when the network was created. + type: int + returned: always + sample: 123456 + created_by: + description: User identifier of who created the network. + type: str + returned: always + sample: "123456" + created_in_seconds: + description: Creation time in seconds. + type: int + returned: always + sample: 123456 + description: + description: Description of the network. + type: str + returned: always + sample: "this is the description" + is_default: + description: Indicates whether this network is the default network. + type: bool + returned: always + sample: false + modified: + description: UNIX timestamp when the network was last modified. + type: int + returned: always + sample: 123456 + modified_by: + description: User identifier of who last modified the network. + type: str + returned: always + sample: "123456" + modified_in_seconds: + description: Modification time in seconds. + type: int + returned: always + sample: 123456 + name: + description: Name of the network. + type: str + returned: always + sample: "ansible_collection_test_network" + owner_uuid: + description: UUID of the owner of the network. + type: str + returned: always + sample: "123456" + scanner_count: + description: Number of scanners associated with this network. + type: int + returned: always + sample: 0 + uuid: + description: UUID of the network. + type: str + returned: always + sample: "123456" + status_code: + description: HTTP status code returned by the API, indicating the result of the operation. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "name", "description", "assets_ttl_days") + specific_spec = { + "network_id": {"required": True, "type": "str"}, + } + argument_spec = {**common_spec, **specific_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + payload_keys = ["name", "description", "assets_ttl_days"] + payload = build_payload(module, payload_keys) + + endpoint = f"networks/{module.params['network_id']}" + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_scan.py b/plugins/modules/update_scan.py new file mode 100644 index 0000000..e9ba729 --- /dev/null +++ b/plugins/modules/update_scan.py @@ -0,0 +1,109 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: update_scan +short_description: Updates the scan configuration. +version_added: "0.0.1" +description: + - This module updates the scan configuration. + - For example, you can enable or disable a scan, change the scan name, description, folder, + scanner, targets, and schedule parameters. + - For more information and request body examples, see https://developer.tenable.com/docs/update-scan-tio + - For information on updating remediation scans and request body examples, https://developer.tenable.com/docs/io-manage-remediation-scans + - With SCAN OPERATOR [24] permissions, policy_id is required. + - This module is made from https://developer.tenable.com/reference/scans-configure docs. + - Requires SCAN OPERATOR [24] and CAN EDIT [64] scan permissions user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan + - valkiriaaquatica.tenable.scan_configuration + - valkiriaaquatica.tenable.scan_credentials +""" + + +EXAMPLES = r""" +- name: Create a scan with simple parameters, host_tagging and frequency scans + update_scan: + scan_id: 123456789 + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + host_tagging: "yes" + refresh_reporting_frequency_scans: 2 + settings: + name: "{{ name_scan_creation }}" + agent_group_id: "{{ agent_group_id_created }}" + +- name: Create a scan with settings and using enviroment creds + update_scan: + scan_id: 123456789 + uuid: "{{ template_scan_uuid }}" + settings: + name: "{{ name_scan_creation }}" + folder_id: 111 + scanner_id: 123456 + launch: "ON_DEMAND" + rrules: "WEEKLY" + timezone: "Atlantic/Madeira" + text_targets: "192.168.1.1,192.168.1.2" + +- name: Create a scan with a file of targets and passing credentials as variables + update_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: 123456789 + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan" + file_targets: scan_targets_file + +- name: Create a scan configuring plugins and enviroment creds + update_scan: + scan_id: 123456789 + uuid: "123456789" + settings: + name: "name" + policy_id: 12345 + plugin_configurations: + - plugin_family_name: "Red Hat Local Security Checks" + plugins: + - plugin_id: "79798" + status: "enabled" + - plugin_id: "79799" + status: "disabled" +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_complex_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec( + "access_key", "secret_key", "scan_id", "uuid", "settings", "credentials", "plugin_configurations" + ) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scans/{module.params['scan_id']}" + payload = build_complex_payload(module.params) + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_scan_routes.py b/plugins/modules/update_scan_routes.py new file mode 100644 index 0000000..cf0b712 --- /dev/null +++ b/plugins/modules/update_scan_routes.py @@ -0,0 +1,83 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: update_scan_routes +short_description: Updates the scan routes for a specified scanner group. +version_added: "0.0.1" +description: + - This module updates the hostnames, hostname wildcards, IP addresses, and IP address ranges that + Tenable Vulnerability Management matches against targets in auto-routed scans. + - For more information about supported route formats, + see https://developer.tenable.com/docs/manage-scan-routing-tio#section-supported-scan-routing-target-formats + - Requires SCAN MANAGER [40] user permissions and CAN EDIT [64] scan permissions as specified + in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/io-scanner-groups-update-routes docs. +options: + routes: + description: + - A list of zero or more hostnames, hostname wildcards, IP addresses, CIDR addresses, or IP ranges. + - For more information about supported route formats, + see https://developer.tenable.com/docs/manage-scan-routing-tio#section-supported-scan-routing-target-formats + type: list + elements: str + required: true +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group +""" + +EXAMPLES = r""" +- name: Update scan routes for a scanner group + update_scan_routes: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 12345 + routes: + - "example.com" + - "192.0.2.0/24" + - "host.domain.com" + +- name: Update scan routes using environment credentials + update_scan_routes: + group_id: 12345 + routes: + - "example.com" + - "192.0.2.0/24" + - "host.domain.com" +""" + +RETURN = r""" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "group_id") + specific_spec = {"routes": {"required": True, "type": "list", "elements": "str"}} + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanner-groups/{module.params['group_id']}/routes" + payload_keys = ["routes"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_scan_status.py b/plugins/modules/update_scan_status.py new file mode 100644 index 0000000..d7a7efc --- /dev/null +++ b/plugins/modules/update_scan_status.py @@ -0,0 +1,80 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: update_scan_status +short_description: Changes the status of a scan. +version_added: "0.0.1" +description: + - This module changes the status of a scan. + - For a list of possible status values, see https://developer.tenable.com/docs/scan-status-tio . + - To update a category you must be an admin or have edit permissions on all tags within the category. + - The module is made from https://developer.tenable.com/reference/scans-read-status. + - RequiresSCAN OPERATOR [24] and CAN VIEW [16] user permissions as specified in the Tenable.io API documentation. +options: + read: + description: + - If true, the scan has been read. + required: true + type: bool +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scan +""" + +EXAMPLES = r""" +- name: Update scan status to scan read + update_scan_status: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "123456" + read: true + +- name: Update scan status to scan not read using enviroment creds + update_scan_status: + scan_id: "123456" + read: false +""" + +RETURN = r""" +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scan_id") + special_args = { + "read": { + "required": True, + "type": "bool", + }, + } + argument_spec = {**common_spec, **special_args} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + payload_keys = ["read"] + payload = build_payload(module, payload_keys) + + endpoint = f"scans/{module.params['scan_id']}/status" + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_scanner.py b/plugins/modules/update_scanner.py new file mode 100644 index 0000000..eb7d2cf --- /dev/null +++ b/plugins/modules/update_scanner.py @@ -0,0 +1,249 @@ +# (c) 2024, Fernando Mendieta Ovejero (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: update_scanner +short_description: Updates the specified scanner. +version_added: "0.0.1" +description: + - This module updates the specified scanner. + - You cannot use this endpoint to assign the scanner to a network object. Instead, use the POST /networks/{network_id}/scanners/{scanner_uuid} endpoint. + - This module is made from https://developer.tenable.com/reference/scanners-edit docs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +options: + name: + description: + - The new name for the scanner. + type: str + required: false + force_plugin_update: + description: + - Pass 1 to force a plugin update. + type: int + required: false + force_ui_update: + description: + - Pass 1 to force a UI update. + type: int + required: false + finish_update: + description: + - Pass 1 to reboot the scanner and run the latest software update (only valid if automatic updates are disabled). + type: int + required: false + registration_code: + description: + - Sets the registration code for the scanner. + type: str + required: false + aws_update_interval: + description: + - Specifies how often, in minutes, the scanner checks in with Tenable Vulnerability Management (Amazon Web Services scanners only). + type: int + required: false +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.scanner +""" + +EXAMPLES = r""" +- name: Update scanner name and force plugin update + update_scanner: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scanner_id: 12345 + name: "New Scanner Name" + force_plugin_update: 1 + +- name: Update scanner registration code using enviroment creds + update_scanner: + scanner_id: 12345 + registration_code: "new_registration_code" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + owner_uuid: + description: The UUID of the owner. + type: str + sample: "169a9fad-c23b-454a-8519-285e30b11d94" + created: + description: Timestamp when the scanner was created. + type: int + sample: 1576171974205 + modified: + description: Timestamp when the scanner was last modified. + type: int + sample: 1579909576455 + container_uuid: + description: UUID of the container. + type: str + sample: "234a6a72-0fe6-4d5b-aaab-9f97774d42da" + uuid: + description: UUID of the scanner. + type: str + sample: "9e226caf-ef74-419a-875e-205e77534cba" + id: + description: ID of the scanner. + type: int + sample: 44741097 + name: + description: Name of the scanner. + type: str + sample: "test13" + type: + description: Type of the scanner. + type: str + sample: "local" + network_name: + description: Network name of the scanner. + type: str + sample: "Default" + default_permissions: + description: Default permissions of the scanner. + type: int + sample: 16 + shared: + description: Whether the scanner is shared. + type: int + sample: 1 + user_permissions: + description: User permissions of the scanner. + type: int + sample: 64 + key: + description: Key of the scanner. + type: str + sample: "e3eeefeacca0d998c466af126549d68ef0f4e0d0ba3ab04a6e59a1d8a8a57079" + system: + description: Whether the scanner is a system scanner. + type: bool + sample: true + linked: + description: Whether the scanner is linked. + type: int + sample: 1 + settings: + description: Settings of the scanner. + type: dict + sample: {} + aws_update_interval: + description: AWS update interval of the scanner. + type: int + sample: 0 + status: + description: Status of the scanner. + type: str + sample: "on" + lce: + description: Whether the scanner is an LCE scanner. + type: bool + sample: false + pvs: + description: Whether the scanner is a PVS scanner. + type: bool + sample: false + aws: + description: Whether the scanner is an AWS scanner. + type: bool + sample: false + industrial_security: + description: Whether the scanner supports industrial security. + type: bool + sample: false + scanner_scanner: + description: Whether the scanner is a scanner scanner. + type: bool + sample: false + webapp: + description: Whether the scanner supports web application scanning. + type: bool + sample: false + group: + description: Whether the scanner is a group scanner. + type: bool + sample: true + can_factory_reset: + description: Whether the scanner can perform a factory reset. + type: bool + sample: false + system_group_scanner: + description: Whether the scanner is a system group scanner. + type: bool + sample: true + system_scanner_scanner: + description: Whether the scanner is a system scanner scanner. + type: bool + sample: false + system_webapp_scanner: + description: Whether the scanner is a system web application scanner. + type: bool + sample: false + supports_remote_logs: + description: Whether the scanner supports remote logs. + type: bool + sample: false + created_in_seconds: + description: Creation timestamp in seconds. + type: int + sample: 1576171974 + modified_in_seconds: + description: Modified timestamp in seconds. + type: int + sample: 1579909576 + network_id: + description: Network ID of the scanner. + type: str + sample: "00000000-0000-0000-0000-000000000000" +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "scanner_id") + specific_spec = { + "name": {"required": False, "type": "str"}, # uniques for this module + "force_plugin_update": {"required": False, "type": "int"}, + "force_ui_update": {"required": False, "type": "int"}, + "finish_update": {"required": False, "type": "int"}, + "registration_code": {"required": False, "type": "str"}, + "aws_update_interval": {"required": False, "type": "int"}, + } + argument_spec = {**common_spec, **specific_spec} + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanners/{module.params['scanner_id']}" + + payload_keys = [ + "name", + "force_plugin_update", + "force_ui_update", + "finish_update", + "registration_code", + "aws_update_interval", + ] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_scanner_group.py b/plugins/modules/update_scanner_group.py new file mode 100644 index 0000000..d6b63aa --- /dev/null +++ b/plugins/modules/update_scanner_group.py @@ -0,0 +1,126 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: update_scanner_group +short_description: Updates a scanner group. +version_added: "0.0.1" +description: + - This module updates the name of a scanner group. + - You cannot use this endpoint to + Assign a scanner group to a network object. Instead, use the assign_scanners module. + Update scan routes configured for the scanner group. Instead, use update_scan_routes module. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. + - This module is made from https://developer.tenable.com/reference/scanner-groups-edit docs. +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.group + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Update the name of a scanner group + update_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: 12345 + name: "New Group Name" + +- name: Update the name of a scanner group using enviroment creds + update_scanner_group: + group_id: 12345 + name: "New Group Name" +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the Tenable.io API. + type: dict + returned: always + contains: + id: + description: The ID of the scanner group. + type: int + name: + description: The new name of the scanner group. + type: str + creation_date: + description: The creation date of the scanner group. + type: int + last_modification_date: + description: The last modification date of the scanner group. + type: int + owner_id: + description: The owner ID of the scanner group. + type: int + owner: + description: The owner of the scanner group. + type: str + owner_uuid: + description: The UUID of the owner. + type: str + default_permissions: + description: The default permissions for the scanner group. + type: int + user_permissions: + description: The user permissions for the scanner group. + type: int + shared: + description: Whether the scanner group is shared. + type: int + scan_count: + description: The number of scans associated with the scanner group. + type: int + scanner_count: + description: The number of scanners in the scanner group. + type: int + uuid: + description: The UUID of the scanner group. + type: str + type: + description: The type of the scanner group. + type: str + network_name: + description: The name of the network associated with the scanner group. + type: str + scanner_id: + description: The ID of the scanner in the scanner group. + type: int + scanner_uuid: + description: The UUID of the scanner in the scanner group. + type: str + owner_name: + description: The name of the owner of the scanner group. + type: str +""" + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "group_id", "name") + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + endpoint = f"scanner-groups/{module.params['group_id']}" + + payload_keys = ["name"] + payload = build_payload(module, payload_keys) + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_tag_category.py b/plugins/modules/update_tag_category.py new file mode 100644 index 0000000..891236d --- /dev/null +++ b/plugins/modules/update_tag_category.py @@ -0,0 +1,101 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: update_tag_category +short_description: Updates the specified tag category +version_added: "0.0.1" +description: + - This module updates the specified tag category. + - To update a category you must be an admin or have edit permissions on all tags within the category. + - The module is made from https://developer.tenable.com/reference/tags-edit-tag-categorydocs. + - Requires SCAN MANAGER [40] user permissions as specified in the Tenable.io API documentation. +options: + description: + description: + - The description of the tag category. + - Must not exceed 3,000 characters. + required: false + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.category + - valkiriaaquatica.tenable.name +""" + +EXAMPLES = r""" +- name: Update tag category + update_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + name: "i_am_the_new_name" + description: "i am the new description" + +- name: Update tag category using enviroment creds + update_tag_category: + category_uuid: "12345" + name: "i_am_the_new_name" +""" + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + type: dict + returned: on success + contains: + data: + description: Contains details about the entity such as creation, update information, and identifiers. + type: dict + returned: always + sample: + created_at: "123456" + created_by: "email@email.com" + description: "this is the description" + name: "name" + product: "1" + reserved: false + updated_at: "123456" + updated_by: "email@email.com" + uuid: "123456" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + argument_spec = get_spec("access_key", "secret_key", "category_uuid", "name", "description") + argument_spec["category_uuid"]["required"] = True + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + payload_keys = ["name", "description"] + payload = build_payload(module, payload_keys) + + endpoint = f"tags/categories/{module.params['category_uuid']}" + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/update_tag_value.py b/plugins/modules/update_tag_value.py new file mode 100644 index 0000000..a3b31b8 --- /dev/null +++ b/plugins/modules/update_tag_value.py @@ -0,0 +1,110 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +--- +module: update_tag_value +short_description: Updates the specified tag value. +version_added: "0.0.1" +description: + - This module updates the specified tag value. + - The tag category can be specified by UUID or name. + - Module made from https://developer.tenable.com/reference/tags-update-tag-value docs. + - Requires SCAN OPERATOR [28] user permissions +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials + - valkiriaaquatica.tenable.tag_value + - valkiriaaquatica.tenable.tag_value_args +""" + + +EXAMPLES = r""" +- name: Update tag value + update_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid }}" + value: "this_is_the_new_value" + +- name: Update tag value with envirometn creds and new filters + update_tag_value: + value_uuid: "123456" + value: "this_is_the_new_value" + filters: + asset: + and: + - field: aws_ec2_name + operator: eq + value: jenkins +""" + + +RETURN = r""" +api_response: + description: Response returned by the Tenable API. + returned: always when a request is made, independent if it is correct or incorrect. + type: dict + contains: + data: + description: Contains detailed information about a category including access controls, metadata, and specific properties related to the category. + type: dict + returned: always + sample: + access_control: + current_user_permissions: "CAN_USE" + category_description: "this is the description" + category_name: "category_name_i_am" + category_uuid: "123456" + consecutive_error_count: 0 + created_at: "date" + created_by: "autobit@nttdata.com" + description: "tag for dev machines" + product: "IO" + saved_search: false + type: "static" + updated_at: "date" + updated_by: "email@email.com" + uuid: "123456" + value: "new_value" + status_code: + description: HTTP status code of the response. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_repeated_special_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key", "access_control", "value_uuid", "value", "description") + special_spec = get_repeated_special_spec("filters") + + argument_spec = {**common_spec, **special_spec} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + payload_keys = [key for key in module.params.keys() if key not in ["access_key", "secret_key", "value_uuid"]] + payload = build_payload(module, payload_keys) + + endpoint = f"tags/values/{module.params['value_uuid']}" + + run_module(module, endpoint, method="PUT", data=payload) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/upload_file.py b/plugins/modules/upload_file.py new file mode 100644 index 0000000..56232fd --- /dev/null +++ b/plugins/modules/upload_file.py @@ -0,0 +1,93 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: upload_file +short_description: Lists all available time zones in Tenable. +version_added: "0.0.1" +description: + - This module uploads a file. + - Requires BASIC [16] user permissions as specified in the Tenable.io API documentation. + - Module made from https://developer.tenable.com/reference/file-upload docs. +options: + no_enc: + description: + - Send value of 1 when uploading an encrypted file. + required: false + type: int + file_path: + description: + - The file to upload. + - The route of the file. + required: true + type: str +author: + - Fernando Mendieta Ovejero (@valkiriaaquatica) +extends_documentation_fragment: + - valkiriaaquatica.tenable.credentials +""" + +EXAMPLES = r""" +- name: Upload a file that is no encrypted + upload_file: + access_key: "your_access_key" + secret_key: "your_secret_key" + file_path: "/tmp/hoss_targets.txt" + +- name: Upload a file encrypted using tenable enviroment creds + upload_file: + file_path: "/tmp/hoss_targets.txt" + no_enc: 1 +""" + +RETURN = r""" +api_response: + description: Detailed information about the response from the API following a file upload operation. + type: dict + returned: always + contains: + data: + description: Contains details about the uploaded file. + type: dict + contains: + fileuploaded: + description: Name of the file that was successfully uploaded. + type: str + returned: always + sample: "thefilename.txt" + status_code: + description: HTTP status code returned by the API, indicating the result of the file upload operation. + type: int + returned: always + sample: 200 +""" + + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.arguments import get_spec +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.simple_requests import run_module_with_file + +from ansible.module_utils.basic import AnsibleModule + + +def main(): + common_spec = get_spec("access_key", "secret_key") + special_args = { # uniques for this module + "file_path": {"type": "str", "required": True}, + "no_enc": {"type": "int", "required": False}, + } + argument_spec = {**common_spec, **special_args} + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + file_path = module.params["file_path"] + + run_module_with_file(module, "file/upload", file_path) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5f6f4d5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[tool.black] +skip-string-normalization = false +line-length = 120 +target-version = ['py37', 'py38'] +extend-exclude = ''' +/( + | plugins/module_utils/_version.py +)/ +''' + +[tool.darker] +revision = "origin/main.." + +src = [ + "plugins", + "tests/unit", + "tests/integration", +] + +[tool.isort] +profile = "black" +force_single_line = true +line_length = 120 + +src_paths = [ + "plugins", + "tests/unit", + "tests/integration", +] + +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "ANSIBLE_CORE", "ANSIBLE_AMAZON_AWS", "ANSIBLE_COMMUNITY_AWS", "LOCALFOLDER"] +known_third_party = ["botocore", "boto3"] +known_ansible_core = ["ansible"] +known_ansible_amazon_aws = ["ansible_collections.amazon.aws"] +known_ansible_community_aws = ["ansible_collections.community.aws"] + +[tool.flynt] +transform-joins = true +exclude = [ + "ec2_metadata_facts", +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/roles/inventory.ini b/roles/inventory.ini new file mode 100644 index 0000000..88e5552 --- /dev/null +++ b/roles/inventory.ini @@ -0,0 +1,9 @@ +# fill this in case you use static inventory with linux and windows groups +[linux] + +[windows] + +[windows:vars] + +[linux:vars] + diff --git a/roles/nessus_agent.yml b/roles/nessus_agent.yml new file mode 100644 index 0000000..f7528bb --- /dev/null +++ b/roles/nessus_agent.yml @@ -0,0 +1,6 @@ +# tasks file for roles +- name: Download, install and Link Tenable Nesuss Agent + gather_facts: true + hosts: windows + roles: + - { role: nesuss_agent, tags: nesuss_agent } \ No newline at end of file diff --git a/roles/nessus_agent/README.md b/roles/nessus_agent/README.md new file mode 100644 index 0000000..cc9539e --- /dev/null +++ b/roles/nessus_agent/README.md @@ -0,0 +1,54 @@ +Nessus Agent Role +========= + +-- not prepraed yet for macOS :( -- + +1. Detects the windows or Linux distro used and downloads the nesssus agent package on host machine from Tenable API to /tmp. +2. Copies the download package to remote machine to /tmp. +3. Install the package. +4. Check for the nessus agent service. +5. Links the agent to Tenable (please fill with the variables needed on vars.yml) +6. Checks if the machine reports in Tenable API . (it is used the ansible_host variable but can be change to any other like mac address for more unique values). +7. Unlinks the agent (delete this if you want to preserve the agent linked). + +Requirements +------------ + +None. + +Role Variables +-------------- + +Variables in main.yml +- tenable_host: "your_secret_host" +- tenable_port: "your_tenable_port" +- tenable_group: "your_tenable_group" +- tenable_network: "your_tenable_network" +- linking_key: "your_linking_key" +- tenable_access_key: "your_access_key" +- tenable_secret_key: "your_secret_key" + +Dependencies +------------ + +- A Windows or Linux machine to target + +Example Playbook +---------------- + +Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too: + + - hosts: all + gather_facts: true + roles: + - { role: nesuss_agent, tags: nesuss_agent } + +License +------- + +BSD + +Author Information +------------------ +Fernando Mendieta Ovejero (@valkiriaaquatica) + diff --git a/roles/nessus_agent/defaults/main.yml b/roles/nessus_agent/defaults/main.yml new file mode 100644 index 0000000..e78ac59 --- /dev/null +++ b/roles/nessus_agent/defaults/main.yml @@ -0,0 +1,2 @@ +--- +# defaults file for roles diff --git a/roles/nessus_agent/handlers/main.yml b/roles/nessus_agent/handlers/main.yml new file mode 100644 index 0000000..3bdda45 --- /dev/null +++ b/roles/nessus_agent/handlers/main.yml @@ -0,0 +1,19 @@ +--- +# handlers file for roles +# linux +- name: service linux + ansible.builtin.systemd: + name: nessusagent.service + state: started + enabled: yes + become: yes + tags: service + +# windows +- name: Make sure nessus agent service is running and enabled windows + ansible.windows.win_service: + name: "Tenable Nessus Agent" + start_mode: auto + state: started + register: resultado + tags: service diff --git a/roles/nessus_agent/meta/main.yml b/roles/nessus_agent/meta/main.yml new file mode 100644 index 0000000..c572acc --- /dev/null +++ b/roles/nessus_agent/meta/main.yml @@ -0,0 +1,52 @@ +galaxy_info: + author: your name + description: your role description + company: your company (optional) + + # If the issue tracker for your role is not on github, uncomment the + # next line and provide a value + # issue_tracker_url: http://example.com/issue/tracker + + # Choose a valid license ID from https://spdx.org - some suggested licenses: + # - BSD-3-Clause (default) + # - MIT + # - GPL-2.0-or-later + # - GPL-3.0-only + # - Apache-2.0 + # - CC-BY-4.0 + license: license (GPL-2.0-or-later, MIT, etc) + + min_ansible_version: 2.1 + + # If this a Container Enabled role, provide the minimum Ansible Container version. + # min_ansible_container_version: + + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + # platforms: + # - name: Fedora + # versions: + # - all + # - 25 + # - name: SomePlatform + # versions: + # - all + # - 1.0 + # - 7 + # - 99.99 + + galaxy_tags: [] + # List tags for your role here, one per line. A tag is a keyword that describes + # and categorizes the role. Users find roles by searching for tags. Be sure to + # remove the '[]' above, if you add tags to this list. + # + # NOTE: A tag is limited to a single word comprised of alphanumeric characters. + # Maximum 20 tags per role. + +dependencies: [] + # List your role dependencies here, one per line. Be sure to remove the '[]' above, + # if you add dependencies to this list. diff --git a/roles/nessus_agent/tasks/common.yml b/roles/nessus_agent/tasks/common.yml new file mode 100644 index 0000000..ea832d6 --- /dev/null +++ b/roles/nessus_agent/tasks/common.yml @@ -0,0 +1,23 @@ + +- name: Link the machine to Tenable.IO in LINUX + valkiriaaquatica.tenable.link_agent_linux: + linking_key: "{{ linking_key }}" + name: "{{ ansible_host }}" + groups: "{{ tenable_group }}" + network: "{{ tenable_network }}" + host: "{{ tenable_host }}" + port: "{{ tenable_port }}" + become: yes + tags: link_linux + when: ansible_facts['os_family'] != 'Windows' + +- name: Link the machine to Tenable.IO in WINDOWS + valkiriaaquatica.tenable.link_agent_windows: + linking_key: "{{ linking_key }}" + name: "{{ ansible_host }}" + groups: "{{ tenable_group }}" + network: "{{ tenable_network }}" + host: "{{ tenable_host }}" + port: "{{ tenable_port }}" + tags: link_windows + when: ansible_facts['os_family'] == 'Windows' diff --git a/roles/nessus_agent/tasks/main.yml b/roles/nessus_agent/tasks/main.yml new file mode 100644 index 0000000..7ace25a --- /dev/null +++ b/roles/nessus_agent/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Download, install and configure nessus agent in Debian distros. + import_tasks: ubuntu.yml + when: ansible_facts['os_family'] == 'Debian' + +- name: Download, install and configure nessus agent in RedHat distros. + import_tasks: rhel.yml + when: ansible_facts['os_family'] == 'RedHat' + +- name: Download, install and configure nessus agent in Suse distros. + import_tasks: suse.yml + when: ansible_facts['os_family'] == 'Suse' + +- name: Download, install and configure nessus agent in Windows distros. + import_tasks: windows.yml + when: ansible_facts['os_family'] == 'Windows' \ No newline at end of file diff --git a/roles/nessus_agent/tasks/rhel.yml b/roles/nessus_agent/tasks/rhel.yml new file mode 100644 index 0000000..b72d666 --- /dev/null +++ b/roles/nessus_agent/tasks/rhel.yml @@ -0,0 +1,76 @@ +--- +# it is recommend to download in local machine and then transfer due to possible problems that host machine can have, e.x: network, permissions, space, internetos +# tested on rhel 9,8 and amazon linux 2023 + +- name: Download Nessus Agent .rpm in local machine + valkiriaaquatica.tenable.download_nessus_agent: + os_distribution: "{{ ansible_facts['distribution'] }}" + os_version: "{{ ansible_facts['distribution_major_version'] }}" + dest: "/tmp/" + register: nessu_agent_download + delegate_to: localhost + tags: download + +- name: Get the package name just downloaded + ansible.builtin.set_fact: + nessus_agent_filename: "{{ nessu_agent_download.filename }}" + delegate_to: localhost + +- name: Copy the .rpm package to the remote host + copy: + src: "/tmp/{{ nessus_agent_filename }}" + dest: "/tmp/{{ nessus_agent_filename }}" + +- name: Install Nessus Agent in RHEL host + ansible.builtin.yum: + name: "/tmp/{{ nessus_agent_filename }}" + state: present + disable_gpg_check: yes + register: install_result + become: yes + notify: service linux + tags: install + +- name: service linux + ansible.builtin.systemd: + name: nessusagent.service + state: started + enabled: yes + become: yes + tags: service + +- name: Including task of linking + ansible.builtin.include_tasks: + file: common.yml + apply: + tags: + - link_linux + tags: link + +- name: Set fact of ansible_host name of remote machine + set_fact: + ansible_host_var: "{{ ansible_host }}" + +- name: Check if the agent is linked in Tenable.io + valkiriaaquatica.tenable.list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: name + operator: eq + value: "{{ ansible_host }}" + register: agent_verification + delegate_to: localhost + tags: verify + +- name: Verify the response is correct + ansible.builtin.assert: + that: + - agent_verification.api_response.status_code == 200 + - agent_verification.api_response.data.agents[0].name == ansible_host + tags: verify + +- name: Unlink agent in Linux + valkiriaaquatica.tenable.unlink_agent_linux: + become: true + tags: unlink diff --git a/roles/nessus_agent/tasks/suse.yml b/roles/nessus_agent/tasks/suse.yml new file mode 100644 index 0000000..d6c85cd --- /dev/null +++ b/roles/nessus_agent/tasks/suse.yml @@ -0,0 +1,72 @@ +# tested on suse 15 and suse 12 + +- name: Download Nessus Agent package in local machine + valkiriaaquatica.tenable.download_nessus_agent: + os_distribution: "{{ ansible_facts['distribution'] }}" + os_version: "{{ ansible_facts['distribution_major_version'] }}" + dest: "/tmp/" + register: nessu_agent_download + delegate_to: localhost + tags: download + +- name: Get the package name just downloaded + ansible.builtin.set_fact: + nessus_agent_filename: "{{ nessu_agent_download.filename }}" + delegate_to: localhost + +- name: Copy the package package to the remote host + copy: + src: "/tmp/{{ nessus_agent_filename }}" + dest: "/tmp/{{ nessus_agent_filename }}" + +- name: Install nessus agent in Suse + become: true + zypper: + name: "/tmp/{{ nessus_agent_filename }}" + state: present + disable_gpg_check: yes + notify: service linux + +- name: service linux + ansible.builtin.systemd: + name: nessusagent.service + state: started + enabled: yes + become: yes + tags: service + +- name: Including task of linking + ansible.builtin.include_tasks: + file: common.yml + apply: + tags: + - link_linux + +- name: Set fact of ansible_host name of remote machine + set_fact: + ansible_host_var: "{{ ansible_host }}" + +- name: Check if the agent is linked in Tenable.io + valkiriaaquatica.tenable.list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: name + operator: eq + value: "{{ ansible_host }}" + register: agent_verification + delegate_to: localhost + tags: verify + +- name: Verify the response is correct + ansible.builtin.assert: + that: + - agent_verification.api_response.status_code == 200 + - agent_verification.api_response.data.agents[0].name == ansible_host + tags: verify + +##### aditionals not for the role +- name: Unlink agent in Linux + valkiriaaquatica.tenable.unlink_agent_linux: + become: true + tags: unlink \ No newline at end of file diff --git a/roles/nessus_agent/tasks/ubuntu.yml b/roles/nessus_agent/tasks/ubuntu.yml new file mode 100644 index 0000000..4602034 --- /dev/null +++ b/roles/nessus_agent/tasks/ubuntu.yml @@ -0,0 +1,74 @@ +--- +# it is recommend to download in local machine and then transfer due to possible problems that host machine can have, e.x: network, permissions, space, internet +# tested on ubuntu: 24,22,20,18 and debian 10 + +- name: Download Nessus Agent .deb in local machine + valkiriaaquatica.tenable.download_nessus_agent: + os_distribution: "{{ ansible_facts['distribution'] }}" + os_version: "{{ ansible_facts['lsb']['major_release'] }}" + dest: "/tmp/" + register: nessu_agent_download + delegate_to: localhost + tags: download + +- name: Get the package name just downloaded + ansible.builtin.set_fact: + nessus_agent_filename: "{{ nessu_agent_download.filename }}" + delegate_to: localhost + +- name: Copy the .deb package to the remote host + copy: + src: "/tmp/{{ nessus_agent_filename }}" + dest: "/tmp/{{ nessus_agent_filename }}" + +- name: Install Nessus Agent in debian host + ansible.builtin.apt: + deb: "/tmp/{{ nessus_agent_filename }}" + state: present + register: install_result + become: yes + notify: service linux + + +- name: service linux + ansible.builtin.systemd: + name: nessusagent.service + state: started + enabled: yes + become: yes + tags: service + +- name: Including task of linking + ansible.builtin.include_tasks: + file: common.yml + apply: + tags: + - link_linux + +- name: Set fact of ansible_host name of remote machine + set_fact: + ansible_host_var: "{{ ansible_host }}" + +- name: Check if the agent is linked in Tenable.io + valkiriaaquatica.tenable.list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: name + operator: eq + value: "{{ ansible_host }}" + register: agent_verification + delegate_to: localhost + tags: verify + +- name: Verify the response is correct + ansible.builtin.assert: + that: + - agent_verification.api_response.status_code == 200 + - agent_verification.api_response.data.agents[0].name == ansible_host + tags: verify + +- name: Unlink agent in Linux + valkiriaaquatica.tenable.unlink_agent_linux: + become: true + tags: unlink \ No newline at end of file diff --git a/roles/nessus_agent/tasks/windows.yml b/roles/nessus_agent/tasks/windows.yml new file mode 100644 index 0000000..3dae7a0 --- /dev/null +++ b/roles/nessus_agent/tasks/windows.yml @@ -0,0 +1,67 @@ +# windows uses the same .exe for all the versions +# tested on windows 2022, 2019, 2016 + +- name: Download Nessus Agent .msi in local machine + valkiriaaquatica.tenable.download_nessus_agent: + os_distribution: "{{ ansible_facts['os_family'] }}" + dest: "/tmp/" + register: nessu_agent_download + delegate_to: localhost + tags: download + +- name: Get the package name just downloaded + ansible.builtin.set_fact: + nessus_agent_filename: "{{ nessu_agent_download.filename }}" + delegate_to: localhost + +- name: Assert Temp directory exists + win_file: + path: "C:\\Temp" + state: directory + +- name: Copy the nessu agent .msi package to the windows host + win_copy: + src: "/tmp/{{ nessus_agent_filename }}" + dest: "C:\\Temp\\{{ nessus_agent_filename }}" + +- name: Install nessus agent + win_package: + path: "C:\\Temp\\{{ nessus_agent_filename }}" + state: present + register: install_result + notify: Make sure nessus agent service is running and enabled windows + + +- name: Including task of linking + ansible.builtin.include_tasks: + file: common.yml + apply: + tags: + - link_windows + +- name: Set fact of ansible_host name of remote machine + set_fact: + ansible_host_var: "{{ ansible_host }}" + +- name: Check if the agent is linked in Tenable.io + valkiriaaquatica.tenable.list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: name + operator: eq + value: "{{ ansible_host_var }}" + register: agent_verification + delegate_to: localhost + tags: verify + +- name: Verify the response is correct + ansible.builtin.assert: + that: + - agent_verification.api_response.status_code == 200 + - agent_verification.api_response.data.agents[0].name == ansible_host + tags: verify + +- name: Unlink the machine nessus agent from Tenable. + valkiriaaquatica.tenable.unlink_agent_windows: + tags: unlink \ No newline at end of file diff --git a/roles/nessus_agent/tests/inventory b/roles/nessus_agent/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/roles/nessus_agent/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/roles/nessus_agent/tests/test.yml b/roles/nessus_agent/tests/test.yml new file mode 100644 index 0000000..d1b0dea --- /dev/null +++ b/roles/nessus_agent/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - roles diff --git a/roles/nessus_agent/vars/main.yml b/roles/nessus_agent/vars/main.yml new file mode 100644 index 0000000..59bfa05 --- /dev/null +++ b/roles/nessus_agent/vars/main.yml @@ -0,0 +1,9 @@ +--- +# vars file for roles +tenable_host: "your_secret_host" +tenable_port: "your_tenable_port" +tenable_group: "your_tenable_group" +tenable_network: "your_tenable_network" +linking_key: "your_linking_key" +tenable_access_key: "your_access_key" +tenable_secret_key: "your_secret_key" \ No newline at end of file diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..43d467b --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,10 @@ +coverage==4.5.4 +flake8 +mock +pytest-xdist +# We should avoid these two modules with py3 +pytest-mock +pytest-forked +mock +tox +pytest-ansible diff --git a/tests/config.yml b/tests/config.yml new file mode 100644 index 0000000..7822854 --- /dev/null +++ b/tests/config.yml @@ -0,0 +1,3 @@ +--- +modules: + python_requires: ">=3.7" diff --git a/tests/integration/ignore.txt b/tests/integration/ignore.txt new file mode 100644 index 0000000..ae9712a --- /dev/null +++ b/tests/integration/ignore.txt @@ -0,0 +1,11 @@ +create_network +delete_network +add_agent_to_group +get_agent_details +add_agent_to_group +get_agent_group_details +get_asset_activity_log +get_asset_details +get_asset_information +get_scanner_details +launch_scan \ No newline at end of file diff --git a/tests/integration/inventory b/tests/integration/inventory new file mode 100644 index 0000000..7c937f8 --- /dev/null +++ b/tests/integration/inventory @@ -0,0 +1,2 @@ +[testgroup] +testhost ansible_connection="local" ansible_pipelining="yes" ansible_python_interpreter="/usr/bin/python3" diff --git a/tests/integration/targets/add_agent_to_group/tasks/main.yml b/tests/integration/targets/add_agent_to_group/tasks/main.yml new file mode 100644 index 0000000..747e20f --- /dev/null +++ b/tests/integration/targets/add_agent_to_group/tasks/main.yml @@ -0,0 +1,75 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id: "{{ agent_group_creation_response.api_response.data.id }}" + + - name: Add agent to a agent group + add_agent_to_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id }}" + agent_id: "{{ agent_id }}" # fix agent + register: add_agent + + - name: Verify agent details is received + ansible.builtin.assert: + that: + - add_agent.api_response.status_code == 200 + - add_agent.changed + + - name: Check if agent exists in agent group + get_agent_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + agent_id: "{{ agent_id }}" + register: agent_details + + - name: Verify if agent exists in group + ansible.builtin.assert: + that: + - agent_details.api_response.status_code == 200 + - agent_details.api_response.data.groups | selectattr('id', 'eq', agent_group_id | int) | list | length > 0 + + always: + - name: Remove agent from group that just created + valkiriaaquatica.tenable.remove_agent_from_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id }}" + agent_id: "{{ agent_id }}" + when: agent_id is defined + register: remove_agent + + - name: Verify agent is well removed + ansible.builtin.assert: + that: + - remove_agent.api_response.status_code == 200 + when: remove_agent.changed + + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id }}" + when: agent_group_id is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed diff --git a/tests/integration/targets/add_or_remove_asset_tags/tasks/main.yml b/tests/integration/targets/add_or_remove_asset_tags/tasks/main.yml new file mode 100644 index 0000000..2137af2 --- /dev/null +++ b/tests/integration/targets/add_or_remove_asset_tags/tasks/main.yml @@ -0,0 +1,101 @@ +- block: + - name: Create tag category + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get id of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + - name: Get name of the tag category + set_fact: + tag_category_name: "{{ tag_category_creation_response.api_response.data.name }}" + + - name: Create tag value + create_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value: "tag_value" + category_name: "{{ tag_category_name }}" + category_uuid: "{{ tag_category_uuid }}" + register: tag_value_creation + + - name: Verify tag value is created + ansible.builtin.assert: + that: + - tag_value_creation.api_response.status_code == 200 + - tag_value_creation.changed + - tag_value_creation.api_response.data.category_uuid == tag_category_uuid + + - name: Get uuid of the tag value just created + set_fact: + tag_value_uuid: "{{ tag_value_creation.api_response.data.uuid }}" + + - name: Add the tag just created to an asset + add_or_remove_asset_tags: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + action: add + assets: "{{ asset_to_add_and_remove_tags_uuid }}" + tags: "{{ tag_value_uuid }}" + register: add_tag_to_asset + + - name: Verify the tag add task was successful + ansible.builtin.assert: + that: + - add_tag_to_asset.api_response.status_code == 200 + - add_tag_to_asset.changed + + - name: Remove the tag just added to an asset + add_or_remove_asset_tags: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + action: remove + assets: "{{ asset_to_add_and_remove_tags_uuid }}" + tags: "{{ tag_value_uuid }}" + register: remove_tag_to_asset + + - name: Verify the tag remove task was successful + ansible.builtin.assert: + that: + - remove_tag_to_asset.api_response.status_code == 200 + - remove_tag_to_asset.changed + + always: + - name: Ensure the tag value is deleted + delete_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid | default('') }}" + when: tag_value_uuid is defined + register: tag_value_deleted_always + + - name: Verify tag value is deleted + ansible.builtin.assert: + that: + - tag_value_deleted_always.api_response.status_code == 200 + when: tag_value_deleted_always.changed + + - name: Ensure the tag category is deleted + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid | default('') }}" + when: tag_category_uuid is defined + register: tag_category_deleted_always + + - name: Verify tag category is deleted + ansible.builtin.assert: + that: + - tag_category_deleted_always.api_response.status_code == 200 + when: tag_category_deleted_always.changed \ No newline at end of file diff --git a/tests/integration/targets/copy_scan/tasks/main.yml b/tests/integration/targets/copy_scan/tasks/main.yml new file mode 100644 index 0000000..1d1e03e --- /dev/null +++ b/tests/integration/targets/copy_scan/tasks/main.yml @@ -0,0 +1,96 @@ +- block: + - name: Create folder + create_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_folder" + register: create_folder_response + + - name: Verify creation was successful + ansible.builtin.assert: + that: + - create_folder_response.api_response.status_code == 200 + - create_folder_response.changed + + - name: Get folder id + set_fact: + folder_id_creation: "{{ create_folder_response.api_response.data.id }}" + + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_name" + register: agent_group_creation_response + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_name" ' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan_creation_creation" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - 'scan_creation.api_response.data.scan.name == "name_scan_creation_creation"' + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + - name: Copy scan + copy_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + folder_id: "{{ folder_id_creation }}" + name: "new scan" + register: copy_scan_response + + - name: Verify the scan is copied + ansible.builtin.assert: + that: + - copy_scan_response.api_response.status_code == 200 + - copy_scan_response.changed + + always: + - name: Ensure agent group is deleted + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created | default('') }}" + when: agent_group_id_created is defined + register: agent_group_deleted_always + + - name: Ensure the scan is deleted + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation | default('') }}" + when: scan_id_creation is defined + register: scan_deleted_always + + - name: Ensure the folder is deleted + delete_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: "{{ folder_id_creation | default('') }}" + when: folder_id_creation is defined + register: folder_deleted_always diff --git a/tests/integration/targets/create_agent_exclusion/tasks/main.yml b/tests/integration/targets/create_agent_exclusion/tasks/main.yml new file mode 100644 index 0000000..0afdc46 --- /dev/null +++ b/tests/integration/targets/create_agent_exclusion/tasks/main.yml @@ -0,0 +1,82 @@ +- block: + - name: Create an exclusion + create_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "exception_test_name" + members: "192.168.1.99, 192.168.1.100" + schedule: + enabled: true + starttime: "2021-04-01 09:00:00" + endtime: "2021-04-01 17:00:00" + timezone: "America/New_York" + rrules: + freq: "WEEKLY" + interval: 1 + byweekday: "MO,TU,WE,TH,FR" + #network_id: "{{ network_uuid_test }}" + register: exclusion_creation + + - name: Verify exclusion was well created + ansible.builtin.assert: + that: + - exclusion_creation.api_response.status_code == 200 + - exclusion_creation.changed + - 'exclusion_creation.api_response.data.name == "exception_test_name"' + - exclusion_creation.api_response.data.schedule.enabled == true + + - name: Get exclusion id + ansible.builtin.set_fact: + exclusion_id: "{{ exclusion_creation.api_response.data.id }}" + + - name: Get information of an exclusion using environment keys + get_exclusion_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_id }}" + register: exclusion_details_response + + - name: Verify exclusion details are received + ansible.builtin.assert: + that: + - exclusion_details_response.api_response.status_code == 200 + + - name: Update an exclusion + valkiriaaquatica.tenable.update_an_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_id }}" + name: "test_exclusion_ansible" + description: "I am the exclusion" + members: "2.5.8.6" + schedule: + enabled: true + starttime: "2024-04-01 09:00:00" + endtime: "2024-04-01 17:00:00" + timezone: "America/New_York" + rrules: + freq: "WEEKLY" + interval: 1 + byweekday: "MO,TU,WE,TH,FR" + register: update_an_exclusion_response + + - name: Verify update exclusion was updated + ansible.builtin.assert: + that: + - update_an_exclusion_response.api_response.status_code == 200 + - update_an_exclusion_response.changed + + always: + - name: Ensure exclusion is deleted + delete_an_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_id | default('') }}" + when: exclusion_id is defined + register: exclusion_deleted_always + + - name: Verify exclusion is deleted + ansible.builtin.assert: + that: + - exclusion_deleted_always.api_response.status_code == 200 + - exclusion_deleted_always.changed diff --git a/tests/integration/targets/create_agent_group/tasks/main.yml b/tests/integration/targets/create_agent_group/tasks/main.yml new file mode 100644 index 0000000..501fb60 --- /dev/null +++ b/tests/integration/targets/create_agent_group/tasks/main.yml @@ -0,0 +1,34 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.id }}" + + + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed diff --git a/tests/integration/targets/create_exclusion/tasks/main.yml b/tests/integration/targets/create_exclusion/tasks/main.yml new file mode 100644 index 0000000..3e2c920 --- /dev/null +++ b/tests/integration/targets/create_exclusion/tasks/main.yml @@ -0,0 +1,92 @@ +- block: + - name: Create agent exclusion + create_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "bit_exception" + description: "description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-07-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: create_agent_exclusion_response + + - name: Verify agent exclusion is created + ansible.builtin.assert: + that: + - create_agent_exclusion_response.api_response.status_code == 200 + - create_agent_exclusion_response.changed + + - name: Set fact of the exclusion id + set_fact: + exclusion_uuid: "{{ create_agent_exclusion_response.api_response.data.id }}" + + - name: Get agent exclusion details + get_agent_exclusion_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + register: exclusion_details + + - name: Verify details are received + ansible.builtin.assert: + that: + - exclusion_details.api_response.status_code == 200 + - exclusion_details.api_response.data.id == create_agent_exclusion_response.api_response.data.id + + - name: List agent exclusions + list_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: list_agent_exclusion_response + + - name: Verify agent exclusions are listed + ansible.builtin.assert: + that: + - '"exclusions" in list_agent_exclusion_response.api_response.data' + + - name: Update agent exclusion + update_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + name: "Updated exclusion name" + description: "Updated description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-07-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: update_agent_exclusion_response + + - name: Verify update was made + ansible.builtin.assert: + that: + - update_agent_exclusion_response.api_response.status_code == 200 + - update_agent_exclusion_response.changed + + always: + - name: Delete exclusion + delete_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ create_agent_exclusion_response.api_response.data.id }}" + when: create_agent_exclusion_response.api_response.data.id is defined + register: exclusion_deletion + + - name: Verify deletion was made + ansible.builtin.assert: + that: + - exclusion_deletion.api_response.status_code == 200 + when: exclusion_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/create_folder/tasks/main.yml b/tests/integration/targets/create_folder/tasks/main.yml new file mode 100644 index 0000000..44732f3 --- /dev/null +++ b/tests/integration/targets/create_folder/tasks/main.yml @@ -0,0 +1,32 @@ +- block: + - name: Create folder + create_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible-collection_folder" + register: create_folder_response + + - name: Verify creation was well + ansible.builtin.assert: + that: + - create_folder_response.api_response.status_code == 200 + - create_folder_response.changed + + - name: Get folder id + set_fact: + folder_id_creation: "{{ create_folder_response.api_response.data.id }}" + + always: + - name: Delete the folder + delete_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: "{{ folder_id_creation }}" + when: folder_id_creation is defined + register: delete_folder_response + + - name: Verify delete was well + ansible.builtin.assert: + that: + - delete_folder_response.api_response.status_code == 200 + - delete_folder_response.changed diff --git a/tests/integration/targets/create_network/tasks/main.yml b/tests/integration/targets/create_network/tasks/main.yml new file mode 100644 index 0000000..145b2cb --- /dev/null +++ b/tests/integration/targets/create_network/tasks/main.yml @@ -0,0 +1,34 @@ +- name: Create a network + create_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_network" + description: "this is the descritpion" + assets_ttl_days: 50 + register: network_creation + +- name: Verify network was well created + ansible.builtin.assert: + that: + - network_creation.api_response.status_code == 200 + - network_creation.changed + - network_creation.api_response.data.assets_ttl_days == 50 + - network_creation.api_response.data.description == "this is the descritpion" + - network_creation.api_response.data.name == "ansible_collection_network" + +- name: Get network uuid + ansible.builtin.set_fact: + network_creation_uuid: "{{ network_creation.api_response.data.uuid }}" + +- name: Delete a network + delete_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "{{ network_creation_uuid }}" + register: network_deleted + +- name: Verify network was deleted + ansible.builtin.assert: + that: + - network_deleted.api_response.status_code == 200 + - network_deleted.changed \ No newline at end of file diff --git a/tests/integration/targets/create_report/tasks/main.yml b/tests/integration/targets/create_report/tasks/main.yml new file mode 100644 index 0000000..6d10139 --- /dev/null +++ b/tests/integration/targets/create_report/tasks/main.yml @@ -0,0 +1,18 @@ +- name: Create a report using plugin id + create_report: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "creation_report" + template_name: "host_vulns_summary" + filters: + - property: "plugin_id" + operator: "eq" + value: [12345] + register: create_report_response + + +- name: Verify the group is well created + ansible.builtin.assert: + that: + - create_report_response.api_response.status_code == 200 + - create_report_response.changed \ No newline at end of file diff --git a/tests/integration/targets/create_scan/tasks/main.yml b/tests/integration/targets/create_scan/tasks/main.yml new file mode 100644 index 0000000..b8b1451 --- /dev/null +++ b/tests/integration/targets/create_scan/tasks/main.yml @@ -0,0 +1,70 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan_creation" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - 'scan_creation.api_response.data.scan.name == "name_scan_creation"' + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed + + + - name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + when: scan_id_creation is defined + register: scan_deletion + + - name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + when: scan_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/create_scanner_group/tasks/main.yml b/tests/integration/targets/create_scanner_group/tasks/main.yml new file mode 100644 index 0000000..d844d4e --- /dev/null +++ b/tests/integration/targets/create_scanner_group/tasks/main.yml @@ -0,0 +1,56 @@ +- block: + - name: Create a new scanner group with default type + create_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" + type: "load_balancing" + register: create_scanner_group_response + + - name: Verify the scanner group is well created + ansible.builtin.assert: + that: + - create_scanner_group_response.changed + + - name: Set fact of scanner group + set_fact: + scanner_group_id: "{{ create_scanner_group_response.api_response.data.id }}" + + - name: Get details of a scanner group + list_scanner_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scanner_group_details_response + + - name: Verify details are received + ansible.builtin.assert: + that: + - list_scanner_group_details_response.api_response.data.id == scanner_group_id|int + + - name: Update the name of the scanner group + update_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + name: "New Group Name" + register: update_scanner_group_response + + - name: Verify the scanner group is well updated + ansible.builtin.assert: + that: + - update_scanner_group_response.changed + + always: + - name: Delete a scanner group + delete_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id | default('') }}" + when: scanner_group_id is defined + register: delete_scanner_group_response + + - name: Verify the scanner group is deleted + ansible.builtin.assert: + that: + - delete_scanner_group_response.changed diff --git a/tests/integration/targets/create_tag_category/tasks/main.yml b/tests/integration/targets/create_tag_category/tasks/main.yml new file mode 100644 index 0000000..e9189e6 --- /dev/null +++ b/tests/integration/targets/create_tag_category/tasks/main.yml @@ -0,0 +1,35 @@ +- block: + - name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get id of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + always: + - name: Delete tag category + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + when: tag_category_uuid is defined + register: tag_deletion + + + - name: Verify tag is deleted + ansible.builtin.assert: + that: + - tag_deletion.api_response.status_code == 200 + when: tag_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/create_tag_value/tasks/main.yml b/tests/integration/targets/create_tag_value/tasks/main.yml new file mode 100644 index 0000000..bc239fe --- /dev/null +++ b/tests/integration/targets/create_tag_value/tasks/main.yml @@ -0,0 +1,101 @@ +- block: + - name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get id of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + + - name: Get name of the tag category + set_fact: + tag_category_name: "{{ tag_category_creation_response.api_response.data.name }}" + + - name: Create tag value + create_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value: "tag_value" + category_name: "{{ tag_category_name }}" + category_uuid: "{{ tag_category_uuid }}" + register: tag_value_creation + + - name: Verify tag value is created + ansible.builtin.assert: + that: + - tag_value_creation.api_response.status_code == 200 + - tag_value_creation.changed + - tag_value_creation.api_response.data.category_uuid == tag_category_uuid + + - name: Get uuid of the tag uuid of the tag value just created + set_fact: + tag_value_uuid: "{{ tag_value_creation.api_response.data.uuid }}" + + - name: Delete tag value + delete_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid }}" + when: tag_value_uuid is defined + register: tag_value_deletion + + - name: Verify tag value is deleted + ansible.builtin.assert: + that: + - tag_value_deletion.api_response.status_code == 200 + when: tag_value_deletion.changed + + #3- name: Create tag value + # create_tag_value: + # access_key: "{{ tenable_access_key }}" + # secret_key: "{{ tenable_secret_key }}" + # value: "tag_value_2" + #category_name: "{{ tag_category_name }}" + #filters: + # asset: + # and: + # - field: aws_ec2_name + # operator: eq + # value: "{{ asset_to_add_tags_name }}" + + #register: tag_value_creation_2 + + #- name: Verify tag value is created + # ansible.builtin.assert: + # that: + # - tag_value_creation_2.api_response.status_code == 200 + # - tag_value_creation_2.changed + # - tag_value_creation_2.api_response.data.category_uuid == tag_category_uuid + + #- name: Get name of the tag uuid of the tag value just created + # set_fact: + # tag_value_uuid_2: "{{ tag_value_creation_2.api_response.data.uuid }}" + + always: + + - name: Delete tag category + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + when: tag_category_uuid is defined + register: tag_deletion + + + - name: Verify tag category is deleted + ansible.builtin.assert: + that: + - tag_deletion.api_response.status_code == 200 + when: tag_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/delete_agent_exclusion/tasks/main.yml b/tests/integration/targets/delete_agent_exclusion/tasks/main.yml new file mode 100644 index 0000000..0e2ddf4 --- /dev/null +++ b/tests/integration/targets/delete_agent_exclusion/tasks/main.yml @@ -0,0 +1,92 @@ +- name: Create agent exclusion + create_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "bit_exception" + description: "description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-7-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: create_agent_exclusion_response + + +- name: Verify agent exclusion is created + ansible.builtin.assert: + that: + - create_agent_exclusion_response.api_response.status_code == 200 + - create_agent_exclusion_response.changed + +- name: Set fact of the exclusin id + set_fact: + exclusion_uuid: "{{ create_agent_exclusion_response.api_response.data.id }}" + +- name: Get agent exclusion details + get_agent_exclusion_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + register: exclusion_details + + +- name: Verify details are received + ansible.builtin.assert: + that: + - exclusion_details.api_response.status_code == 200 + - exclusion_details.api_response.data.id == create_agent_exclusion_response.api_response.data.id + +- name: List agent exclusions + list_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: list_agent_exclusion_response + +- name: List agent exclusions + ansible.builtin.assert: + that: + - '"exclusions" in list_agent_exclusion_response.api_response.data' + + +- name: Update agent exclusion + update_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + name: "Updated exclusion name" + description: "Updated description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-07-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: update_agent_exclusion_response + +- name: Verify update was made + ansible.builtin.assert: + that: + - update_agent_exclusion_response.api_response.status_code == 200 + - update_agent_exclusion_response.changed + +- name: Delete exclusion + delete_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ create_agent_exclusion_response.api_response.data.id }}" + register: exclusion_deletion + +- name: Verify deletion was made + ansible.builtin.assert: + that: + - exclusion_deletion.api_response.status_code == 200 + - exclusion_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/delete_agent_group/tasks/main.yml b/tests/integration/targets/delete_agent_group/tasks/main.yml new file mode 100644 index 0000000..45ab299 --- /dev/null +++ b/tests/integration/targets/delete_agent_group/tasks/main.yml @@ -0,0 +1,35 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id: "{{ agent_group_creation_response.api_response.data.id }}" + + always: + + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id }}" + when: agent_group_id is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed \ No newline at end of file diff --git a/tests/integration/targets/delete_an_exclusion/tasks/main.yml b/tests/integration/targets/delete_an_exclusion/tasks/main.yml new file mode 100644 index 0000000..497a2bb --- /dev/null +++ b/tests/integration/targets/delete_an_exclusion/tasks/main.yml @@ -0,0 +1,35 @@ +- block: + - name: Create a exclusion + create_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "exception_test_name" + members: "192.168.1.99, 192.168.1.100" + register: exclusion_creation + + - name: Verify exclusion was well created + ansible.builtin.assert: + that: + - exclusion_creation.api_response.status_code == 200 + - exclusion_creation.changed + - 'exclusion_creation.api_response.data.name == "exception_test_name"' + + - name: Get exclusion id + ansible.builtin.set_fact: + exclusion_id_response: "{{ exclusion_creation.api_response.data.id }}" + + always: + - name: Delete the exclusion + delete_an_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_id_response }}" + when: exclusion_id_response is defined + register: delete_exclusion + + + - name: Verify exclusion was deleted + ansible.builtin.assert: + that: + - delete_exclusion.api_response.status_code == 200 + when: delete_exclusion.changed \ No newline at end of file diff --git a/tests/integration/targets/delete_folder/tasks/main.yml b/tests/integration/targets/delete_folder/tasks/main.yml new file mode 100644 index 0000000..77c1a71 --- /dev/null +++ b/tests/integration/targets/delete_folder/tasks/main.yml @@ -0,0 +1,33 @@ +- block: + - name: Create folder + create_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible-collection_folder" + register: create_folder_response + + - name: Verify creation was well + ansible.builtin.assert: + that: + - create_folder_response.api_response.status_code == 200 + - create_folder_response.changed + + - name: Get folder id + set_fact: + folder_id_creation: "{{ create_folder_response.api_response.data.id }}" + + always: + + - name: Delete the folder + delete_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: "{{ folder_id_creation }}" + when: folder_id_creation is defined + register: delete_folder_response + + - name: Verify delete was well + ansible.builtin.assert: + that: + - delete_folder_response.api_response.status_code == 200 + when: delete_folder_response.changed \ No newline at end of file diff --git a/tests/integration/targets/delete_network/tasks/main.yml b/tests/integration/targets/delete_network/tasks/main.yml new file mode 100644 index 0000000..145b2cb --- /dev/null +++ b/tests/integration/targets/delete_network/tasks/main.yml @@ -0,0 +1,34 @@ +- name: Create a network + create_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_network" + description: "this is the descritpion" + assets_ttl_days: 50 + register: network_creation + +- name: Verify network was well created + ansible.builtin.assert: + that: + - network_creation.api_response.status_code == 200 + - network_creation.changed + - network_creation.api_response.data.assets_ttl_days == 50 + - network_creation.api_response.data.description == "this is the descritpion" + - network_creation.api_response.data.name == "ansible_collection_network" + +- name: Get network uuid + ansible.builtin.set_fact: + network_creation_uuid: "{{ network_creation.api_response.data.uuid }}" + +- name: Delete a network + delete_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "{{ network_creation_uuid }}" + register: network_deleted + +- name: Verify network was deleted + ansible.builtin.assert: + that: + - network_deleted.api_response.status_code == 200 + - network_deleted.changed \ No newline at end of file diff --git a/tests/integration/targets/delete_scan/tasks/main.yml b/tests/integration/targets/delete_scan/tasks/main.yml new file mode 100644 index 0000000..7617b5a --- /dev/null +++ b/tests/integration/targets/delete_scan/tasks/main.yml @@ -0,0 +1,71 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan_creation" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - 'scan_creation.api_response.data.scan.name == "name_scan_creation"' + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed + + - name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + when: scan_id_creation is defined + register: scan_deletion + + - name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + when: scan_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/delete_scanner_group/tasks/main.yml b/tests/integration/targets/delete_scanner_group/tasks/main.yml new file mode 100644 index 0000000..0215df7 --- /dev/null +++ b/tests/integration/targets/delete_scanner_group/tasks/main.yml @@ -0,0 +1,58 @@ +- block: + - name: Create a new scanner group with default type + create_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" + type: "load_balancing" + register: create_scanner_group_response + + - name: Verify the scanner group is well created + ansible.builtin.assert: + that: + - create_scanner_group_response.changed + + - name: Set fact of scanner grup + set_fact: + scanner_group_id: "{{ create_scanner_group_response.api_response.data.id }}" + + - name: Get details of a scanner group + list_scanner_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scanner_group_details_response + + - name: Verify details are received + ansible.builtin.assert: + that: + - list_scanner_group_details_response.api_response.data.id == scanner_group_id|int + + - name: Update the name of the scanner group + update_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + name: "New Group Name" + register: update_scanner_group_response + + + - name: Verify the scanner group is well updated + ansible.builtin.assert: + that: + - update_scanner_group_response.changed + + always: + - name: Delete a scanner group + delete_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + when: scanner_group_id is defined + register: ldelete_scanner_group_response + + - name: Verify the scanner group is deleted + ansible.builtin.assert: + that: + - ldelete_scanner_group_response.changed + when: ldelete_scanner_group_response.changed \ No newline at end of file diff --git a/tests/integration/targets/delete_tag_category/tasks/main.yml b/tests/integration/targets/delete_tag_category/tasks/main.yml new file mode 100644 index 0000000..b1492e5 --- /dev/null +++ b/tests/integration/targets/delete_tag_category/tasks/main.yml @@ -0,0 +1,35 @@ +- block: + - name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get if of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + always: + - name: Delete tag category + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + when: tag_category_uuid is defined + register: tag_deletion + + + - name: Verify tag is deleted + ansible.builtin.assert: + that: + - tag_deletion.api_response.status_code == 200 + when: tag_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/download_nessus_agent/tasks/main.yml b/tests/integration/targets/download_nessus_agent/tasks/main.yml new file mode 100644 index 0000000..ce019c6 --- /dev/null +++ b/tests/integration/targets/download_nessus_agent/tasks/main.yml @@ -0,0 +1,198 @@ +# here i can try to emulate with the differente container images in +# https://github.com/ansible/ansible/blob/devel/test/lib/ansible_test/_data/completion/docker.txt + +- name: Ensure requests module exists in the target machine + ansible.builtin.command: pip install requests # pip is default pip3 in ubuntu 22 and 20 + +- name: Download latest nessus agent and saved it in /tmp directory + download_nessus_agent: + os_distribution: "{{ ansible_facts['distribution'] }}" + os_version: "{{ ansible_facts['lsb']['major_release'] }}" + dest: "/tmp/" + register: nessus_agent_download + +- name: Verify package was download + ansible.builtin.assert: + that: + - '"filename" in nessus_agent_download' + - '"download sucessefully" in nessus_agent_download.msg' + - nessus_agent_download.changed + + # download other packages but not with vars +- name: Download latest Amazon Linux nesssus agent + download_nessus_agent: + os_distribution: "Amazon" + dest: "/tmp/" + register: amazon_package + +- name: Verify amazon package was download + ansible.builtin.assert: + that: + - '"filename" in amazon_package' + - '"download sucessefully" in amazon_package.msg' + - amazon_package.changed + +- name: Download latest CentOS 7 nesssus agent + download_nessus_agent: + os_distribution: "CentOS" + os_version: "7" + dest: "/tmp/" + register: centos_package_7 + +- name: Verify centos package was download + ansible.builtin.assert: + that: + - '"filename" in centos_package_7' + - '"download sucessefully" in centos_package_7.msg' + - centos_package_7.changed + +- name: Download latest CentOS 8 nesssus agent + download_nessus_agent: + os_distribution: "CentOS" + os_version: "8" + dest: "/tmp/" + register: centos_package_8 + +- name: Verify centos 8 package was download + ansible.builtin.assert: + that: + - '"filename" in centos_package_8' + - '"download sucessefully" in centos_package_8.msg' + - centos_package_8.changed + +- name: Download latest OracleLinux 7 nesssus agent + download_nessus_agent: + os_distribution: "OracleLinux" + os_version: "7" + dest: "/tmp/" + register: oracle_package_7 + +- name: Verify OracleLinux 7 package was download + ansible.builtin.assert: + that: + - '"filename" in oracle_package_7' + - '"download sucessefully" in oracle_package_7.msg' + - oracle_package_7.changed + +- name: Download latest OracleLinux 8 nesssus agent + download_nessus_agent: + os_distribution: "OracleLinux" + os_version: "8" + dest: "/tmp/" + register: oracle_package_8 + +- name: Verify OracleLinux 7 package was download + ansible.builtin.assert: + that: + - '"filename" in oracle_package_8' + - '"download sucessefully" in oracle_package_8.msg' + - oracle_package_8.changed + +- name: Download latest Debian nesssus agent + download_nessus_agent: + os_distribution: "Debian" + dest: "/tmp/" + register: debian + +- name: Verify Debian package was download + ansible.builtin.assert: + that: + - '"filename" in debian' + - '"download sucessefully" in debian.msg' + - debian.changed + +- name: Download latest RedHat 6 nesssus agent + download_nessus_agent: + os_distribution: "RedHat" + os_version: "6" + dest: "/tmp/" + register: red_hat_6 + +- name: Verify RedHat 6 package was download + ansible.builtin.assert: + that: + - '"filename" in red_hat_6' + - '"download sucessefully" in red_hat_6.msg' + - red_hat_6.changed + +- name: Download latest RedHat 7 nesssus agent + download_nessus_agent: + os_distribution: "RedHat" + os_version: "7" + dest: "/tmp/" + register: red_hat_7 + +- name: Verify RedHat 7 package was download + ansible.builtin.assert: + that: + - '"filename" in red_hat_7' + - '"download sucessefully" in red_hat_7.msg' + - red_hat_7.changed + +- name: Download latest RedHat 8 nesssus agent + download_nessus_agent: + os_distribution: "RedHat" + os_version: "8" + dest: "/tmp/" + register: red_hat_8 + +- name: Verify RedHat 8 package was download + ansible.builtin.assert: + that: + - '"filename" in red_hat_8' + - '"download sucessefully" in red_hat_8.msg' + - red_hat_8.changed + +- name: Download latest SLES 12 nesssus agent + download_nessus_agent: + os_distribution: "SLES" + os_version: "12" + dest: "/tmp/" + register: sles_12 + +- name: Verify SLES 12 package was download + ansible.builtin.assert: + that: + - '"filename" in sles_12' + - '"download sucessefully" in sles_12.msg' + - sles_12.changed + +- name: Download latest SLES 15 nesssus agent + download_nessus_agent: + os_distribution: "SLES" + os_version: "15" + dest: "/tmp/" + register: sles_15 + +- name: Verify SLES 15 package was download + ansible.builtin.assert: + that: + - '"filename" in sles_15' + - '"download sucessefully" in sles_15.msg' + - sles_15.changed + +- name: Download latest Ubuntu nesssus agent + download_nessus_agent: + os_distribution: "Ubuntu" + dest: "/tmp/" + register: ubuntu + +- name: Verify Ubuntu package was download + ansible.builtin.assert: + that: + - '"filename" in ubuntu' + - '"download sucessefully" in ubuntu.msg' + - ubuntu.changed + +- name: Download latest Windows nesssus agent + download_nessus_agent: + os_distribution: "Windows" + dest: "/tmp/" + register: windows + +- name: Verify windows package was download + ansible.builtin.assert: + that: + - '"filename" in windows' + - '"download sucessefully" in windows.msg' + - windows.changed \ No newline at end of file diff --git a/tests/integration/targets/enabled_schedule/tasks/main.yml b/tests/integration/targets/enabled_schedule/tasks/main.yml new file mode 100644 index 0000000..33ad433 --- /dev/null +++ b/tests/integration/targets/enabled_schedule/tasks/main.yml @@ -0,0 +1,103 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan_creation" + agent_group_id: "{{ agent_group_id_created }}" + rrules: "FREQ=WEEKLY;INTERVAL=4;BYDAY=SU" + enabled: True + register: scan_creation + + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - 'scan_creation.api_response.data.scan.name == "name_scan_creation"' + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + - name: Disable the scan schedule + enable_schedule: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + enabled: False + register: enable_schedule_response + + - name: Verify the scan is not schedule + ansible.builtin.assert: + that: + - enable_schedule_response.api_response.status_code == 200 + - enable_schedule_response.changed + + - name: Disable the scan schedule + enable_schedule: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + #scan_id: "{{ scan_id_creation }}" + scan_id: "{{ scan_id_creation }}" + enabled: True + register: enable_schedule_response + + - name: Verify the scan is not schedule + ansible.builtin.assert: + that: + - enable_schedule_response.api_response.status_code == 200 + - enable_schedule_response.changed + + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed + + + - name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + when: scan_id_creation is defined + register: scan_deletion + + - name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + when: scan_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/get_agent_details/tasks/main.yml b/tests/integration/targets/get_agent_details/tasks/main.yml new file mode 100644 index 0000000..743b695 --- /dev/null +++ b/tests/integration/targets/get_agent_details/tasks/main.yml @@ -0,0 +1,13 @@ +- name: Get agent details + get_agent_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + agent_id: "{{ agent_id }}" + register: agent_details + + +- name: Verify agent details is received + ansible.builtin.assert: + that: + - agent_details.api_response.status_code == 200 + - 'agent_details.api_response.data.id == agent_id' \ No newline at end of file diff --git a/tests/integration/targets/get_agent_exclusion_details/tasks/main.yml b/tests/integration/targets/get_agent_exclusion_details/tasks/main.yml new file mode 100644 index 0000000..ad2d085 --- /dev/null +++ b/tests/integration/targets/get_agent_exclusion_details/tasks/main.yml @@ -0,0 +1,95 @@ +- block: + - name: Create agent exclusion + create_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "bit_exception" + description: "description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-7-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: create_agent_exclusion_response + + + - name: Verify agent exclusion is created + ansible.builtin.assert: + that: + - create_agent_exclusion_response.api_response.status_code == 200 + - create_agent_exclusion_response.changed + + - name: Set fact of the exclusin id + set_fact: + exclusion_uuid: "{{ create_agent_exclusion_response.api_response.data.id }}" + + - name: Get agent exclusion details + get_agent_exclusion_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + register: exclusion_details + + + - name: Verify details are received + ansible.builtin.assert: + that: + - exclusion_details.api_response.status_code == 200 + - exclusion_details.api_response.data.id == create_agent_exclusion_response.api_response.data.id + + - name: List agent exclusions + list_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: list_agent_exclusion_response + + - name: List agent exclusions + ansible.builtin.assert: + that: + - '"exclusions" in list_agent_exclusion_response.api_response.data' + + + - name: Update agent exclusion + update_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + name: "Updated exclusion name" + description: "Updated description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-07-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: update_agent_exclusion_response + + - name: Verify update was made + ansible.builtin.assert: + that: + - update_agent_exclusion_response.api_response.status_code == 200 + - update_agent_exclusion_response.changed + + always: + - name: Delete exclusion + delete_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ create_agent_exclusion_response.api_response.data.id }}" + when: create_agent_exclusion_response.api_response.data.id is defined + register: exclusion_deletion + + - name: Verify deletion was made + ansible.builtin.assert: + that: + - exclusion_deletion.api_response.status_code == 200 + when: exclusion_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/get_agent_group_details/tasks/main.yml b/tests/integration/targets/get_agent_group_details/tasks/main.yml new file mode 100644 index 0000000..e36e9bd --- /dev/null +++ b/tests/integration/targets/get_agent_group_details/tasks/main.yml @@ -0,0 +1,61 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.id }}" + + - name: Get agents from specified group + valkiriaaquatica.tenable.get_agent_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + wildcard_text: test + limit: 1 + register: agents_test + + - name: Verify agents are well reeived + ansible.builtin.assert: + that: + - agents_test.api_response.status_code == 200 + - agents_test.api_response.data.pagination.limit == 1 + + - name: Get agents from group filtering + valkiriaaquatica.tenable.get_agent_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + register: agents_linux + + + - name: Verify agents are well reeived + ansible.builtin.assert: + that: + - agents_test.api_response.status_code == 200 + + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed diff --git a/tests/integration/targets/get_asset_activity_log/tasks/main.yml b/tests/integration/targets/get_asset_activity_log/tasks/main.yml new file mode 100644 index 0000000..39332af --- /dev/null +++ b/tests/integration/targets/get_asset_activity_log/tasks/main.yml @@ -0,0 +1,13 @@ +- name: Get asset activity log + get_asset_activity_log: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "{{ asset_to_add_and_remove_tags_uuid }}" + register: get_asset_activity_log_response + + +- name: Verify response + ansible.builtin.assert: + that: + - get_asset_activity_log_response.api_response.status_code == 200 + - '"activity" in get_asset_activity_log_response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/get_asset_details/tasks/main.yml b/tests/integration/targets/get_asset_details/tasks/main.yml new file mode 100644 index 0000000..142136f --- /dev/null +++ b/tests/integration/targets/get_asset_details/tasks/main.yml @@ -0,0 +1,13 @@ +- name: Get asset details + get_asset_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "asset_id_with_vulns" + register: asset_details + + +- name: Verify asset details is received + ansible.builtin.assert: + that: + - asset_details.api_response.status_code == 200 + - 'asset_details.api_response.data.id == asset_id_with_vulns' \ No newline at end of file diff --git a/tests/integration/targets/get_asset_information/tasks/main.yml b/tests/integration/targets/get_asset_information/tasks/main.yml new file mode 100644 index 0000000..c318798 --- /dev/null +++ b/tests/integration/targets/get_asset_information/tasks/main.yml @@ -0,0 +1,13 @@ +- name: Get asset information + get_asset_information: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "{{ asset_to_add_and_remove_tags_uuid }}" + register: get_asset_information_response + + +- name: Verify response + ansible.builtin.assert: + that: + - get_asset_information_response.api_response.status_code == 200 + - get_asset_information_response.api_response.data.info.uuid == asset_to_add_and_remove_tags_uuid \ No newline at end of file diff --git a/tests/integration/targets/get_asset_vulnerability_details/tasks/main.yml b/tests/integration/targets/get_asset_vulnerability_details/tasks/main.yml new file mode 100644 index 0000000..2f0d85b --- /dev/null +++ b/tests/integration/targets/get_asset_vulnerability_details/tasks/main.yml @@ -0,0 +1,33 @@ +- name: Get all vulns from asset + list_asset_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "{{ asset_to_add_and_remove_tags_uuid }}" + register: asset_info_vulns + +- name: Verify that there are vulns of the asset + ansible.builtin.assert: + that: + - asset_info_vulns.api_response.status_code == 200 + - '"total_asset_count" in asset_info_vulns.api_response.data' + - 'asset_info_vulns.api_response.data.total_vulnerability_count > 0' + +- name: Get the plugin from the asset vulns + set_fact: + plugin_from_vuls: "{{ asset_info_vulns.api_response.data.vulnerabilities[0].plugin_id }}" + + +- name: Get details of the plugin + get_asset_vulnerability_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "{{ asset_to_add_and_remove_tags_uuid }}" + plugin_id: "{{ plugin_from_vuls }}" + register: get_asset_vulnerability_details_info + + +- name: Verify that there are vulns of the asset + ansible.builtin.assert: + that: + - get_asset_vulnerability_details_info.api_response.status_code == 200 + - '"info" in get_asset_vulnerability_details_info.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/get_category_details/tasks/main.yml b/tests/integration/targets/get_category_details/tasks/main.yml new file mode 100644 index 0000000..cdfeaf8 --- /dev/null +++ b/tests/integration/targets/get_category_details/tasks/main.yml @@ -0,0 +1,47 @@ +- block: + - name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get id of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + - name: Get tag categories details + get_category_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + register: category_details + + - name: Verify data from tag category + ansible.builtin.assert: + that: + - category_details.api_response.status_code == 200 + - 'category_details.api_response.data.name == "ansible_collection_tag_category"' + always: + - name: Delete tag category + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + when: tag_category_uuid is defined + register: tag_deletion + + + - name: Verify tag is deleted + ansible.builtin.assert: + that: + - tag_deletion.api_response.status_code == 200 + when: tag_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/get_exclusion_details/tasks/main.yml b/tests/integration/targets/get_exclusion_details/tasks/main.yml new file mode 100644 index 0000000..82b7866 --- /dev/null +++ b/tests/integration/targets/get_exclusion_details/tasks/main.yml @@ -0,0 +1,47 @@ +- block: + - name: Create a exclusion + create_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "exception_test_name" + members: "192.168.1.99, 192.168.1.100" + register: exclusion_creation + + - name: Verify exclusion was well created + ansible.builtin.assert: + that: + - exclusion_creation.api_response.status_code == 200 + - exclusion_creation.changed + - 'exclusion_creation.api_response.data.name == "exception_test_name"' + + - name: Get exclusion id + ansible.builtin.set_fact: + exclusion_id_created: "{{ exclusion_creation.api_response.data.id }}" + + - name: Get exclusion details + get_exclusion_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_id_created }}" + register: exclusion_details + + - name: Verify exclusion details + ansible.builtin.assert: + that: + - exclusion_details.api_response.status_code == 200 + - 'exclusion_details.api_response.data.name == "exception_test_name"' + + always: + - name: Delete the exclusion + delete_an_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_id_created }}" + when: exclusion_id_created is defined + register: delete_exclusion + + - name: Verify exclusion was deleted + ansible.builtin.assert: + that: + - delete_exclusion.api_response.status_code == 200 + when: delete_exclusion.changed \ No newline at end of file diff --git a/tests/integration/targets/get_latest_scan_status/tasks/main.yml b/tests/integration/targets/get_latest_scan_status/tasks/main.yml new file mode 100644 index 0000000..401464b --- /dev/null +++ b/tests/integration/targets/get_latest_scan_status/tasks/main.yml @@ -0,0 +1,83 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan_creation" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - 'scan_creation.api_response.data.scan.name == "name_scan_creation"' + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + - name: Get the scan status + get_latest_scan_status: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + register: scan_status + + - name: Verify the scan status response is correcta + ansible.builtin.assert: + that: + - scan_status.api_response.status_code == 200 + + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed + + - name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + when: scan_id_creation is defined + register: scan_deletion + + - name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + when: scan_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/get_network_details/tasks/main.yml b/tests/integration/targets/get_network_details/tasks/main.yml new file mode 100644 index 0000000..084c173 --- /dev/null +++ b/tests/integration/targets/get_network_details/tasks/main.yml @@ -0,0 +1,34 @@ +--- +- name: Get network details of a specified network + get_network_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "{{ network_uuid }}" + register: network_details + +- name: Verificar la respuesta de redes filtradas + ansible.builtin.assert: + that: + - network_details.api_response.status_code == 200 + - '"data" in network_details.api_response' + - '"created" in network_details.api_response.data' + - '"uuid" in network_details.api_response.data' + - 'network_details.api_response.data.uuid == network_uuid' + + +- name: Get assets with log4j and with enviroment credential variables + valkiriaaquatica.tenable.list_asset_with_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: plugin.name + operator: match + value: log4j + register: assets_with_lo4gj + +- name: Verify that there are assets returned + ansible.builtin.assert: + that: + - assets_with_lo4gj.api_response.status_code == 200 + - '"assets" in assets_with_lo4gj.api_response.data' + - 'assets_with_lo4gj.api_response.data.assets | length > 0' \ No newline at end of file diff --git a/tests/integration/targets/get_plugin_details/tasks/main.yml b/tests/integration/targets/get_plugin_details/tasks/main.yml new file mode 100644 index 0000000..acd4dea --- /dev/null +++ b/tests/integration/targets/get_plugin_details/tasks/main.yml @@ -0,0 +1,12 @@ +- name: Get plugin details + get_plugin_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + plugin_id: "56" + register: plugin_details + + +- name: Verify plugin details are received + ansible.builtin.assert: + that: + - plugin_details.api_response.status_code == 200 \ No newline at end of file diff --git a/tests/integration/targets/get_report_status/tasks/main.yml b/tests/integration/targets/get_report_status/tasks/main.yml new file mode 100644 index 0000000..afcd3b1 --- /dev/null +++ b/tests/integration/targets/get_report_status/tasks/main.yml @@ -0,0 +1,35 @@ +- name: Create a report using plugin id + create_report: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "creation_report" + template_name: "host_vulns_summary" + filters: + - property: "plugin_id" + operator: "eq" + value: [12345] + register: create_report_response + +- name: Verify the group is well created + ansible.builtin.assert: + that: + - create_report_response.api_response.status_code == 200 + - create_report_response.changed + +- name: Get report uuid + set_fact: + report_uuid: "{{ create_report_response.api_response.data.uuid }}" + + +- name: List plugin in families + valkiriaaquatica.tenable.get_report_status: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + report_uuid: "{{ report_uuid }}" + register: get_report_status + +- name: Verify the group is well created + ansible.builtin.assert: + that: + - get_report_status.api_response.status_code == 200 + - '"status" in get_report_status.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/get_scan_count/tasks/main.yml b/tests/integration/targets/get_scan_count/tasks/main.yml new file mode 100644 index 0000000..2eba7f9 --- /dev/null +++ b/tests/integration/targets/get_scan_count/tasks/main.yml @@ -0,0 +1,25 @@ +- name: Get scan count + get_scan_count: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + active: True + register: get_scan_count_response + +- name: Verify the scan count response + ansible.builtin.assert: + that: + - get_scan_count_response.api_response.status_code == 200 + - '"count" in get_scan_count_response.api_response.data' + +- name: Get scan count + get_scan_count: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + active: False + register: get_scan_count_response_two + +- name: Verify the scan count response + ansible.builtin.assert: + that: + - get_scan_count_response_two.api_response.status_code == 200 + - '"count" in get_scan_count_response_two.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/get_scan_details/tasks/main.yml b/tests/integration/targets/get_scan_details/tasks/main.yml new file mode 100644 index 0000000..adf1fd5 --- /dev/null +++ b/tests/integration/targets/get_scan_details/tasks/main.yml @@ -0,0 +1,85 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan_creation" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - 'scan_creation.api_response.data.scan.name == "name_scan_creation"' + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + - name: Get scan details from scan just created + get_scan_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + register: scan_folder + + - name: Verify scan data is received + ansible.builtin.assert: + that: + - scan_folder.api_response.status_code == 200 + - '"history" in scan_folder.api_response.data' + + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + - agent_group_deletation.changed + + always: + - name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + when: scan_id_creation is defined + register: scan_deletion + + - name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + when: scan_deletion.changed + + \ No newline at end of file diff --git a/tests/integration/targets/get_scanner_details/tasks/main.yml b/tests/integration/targets/get_scanner_details/tasks/main.yml new file mode 100644 index 0000000..198f7af --- /dev/null +++ b/tests/integration/targets/get_scanner_details/tasks/main.yml @@ -0,0 +1,82 @@ +- name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_tenable_collection" + register: agent_group_creation_response + +- name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == agent_group_tenable_collection ' + +- name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + +- name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "{{ name_scan_creation }}" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + +- name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - scan_creation.api_response.data.scan.name == name_scan_creation + +- name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + +- name: Get scanner details + get_scanner_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scanner_id: "{{ scan_id_creation }}" + register: scanner_details + +- name: Verify network was well created + ansible.builtin.assert: + that: + - scanner_details.api_response.status_code == 200 + - scanner_details.api_response.data.uuid == scan_id_creation + + +- name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + register: agent_group_deletation + +- name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + - agent_group_deletation.changed + + +- name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + register: scan_deletion + +- name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + - scan_deletion.changed diff --git a/tests/integration/targets/get_server_status/tasks/main.yml b/tests/integration/targets/get_server_status/tasks/main.yml new file mode 100644 index 0000000..b755878 --- /dev/null +++ b/tests/integration/targets/get_server_status/tasks/main.yml @@ -0,0 +1,10 @@ +- name: Get server status + get_server_status: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: get_server_status_response + +- name: Verify the response is ok + ansible.builtin.assert: + that: + - get_server_status_response.api_response.status_code == 200 \ No newline at end of file diff --git a/tests/integration/targets/get_tag_values/tasks/main.yml b/tests/integration/targets/get_tag_values/tasks/main.yml new file mode 100644 index 0000000..daac6bf --- /dev/null +++ b/tests/integration/targets/get_tag_values/tasks/main.yml @@ -0,0 +1,87 @@ +- block: + - name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get id of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + + - name: Get name of the tag category + set_fact: + tag_category_name: "{{ tag_category_creation_response.api_response.data.name }}" + + - name: Create tag value + create_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value: "tag_value" + category_name: "{{ tag_category_name }}" + category_uuid: "{{ tag_category_uuid }}" + register: tag_value_creation + + - name: Verify tag value is created + ansible.builtin.assert: + that: + - tag_value_creation.api_response.status_code == 200 + - tag_value_creation.changed + - tag_value_creation.api_response.data.category_uuid == tag_category_uuid + + - name: Get name of the tag uuid of the tag value just created + set_fact: + tag_value_uuid: "{{ tag_value_creation.api_response.data.uuid }}" + + - name: Get tag value details + get_tag_value_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid }}" + register: tag_value_details + + - name: Verify tag value details is receviec + ansible.builtin.assert: + that: + - tag_value_details.api_response.status_code == 200 + - tag_value_details.api_response.data.uuid == tag_value_uuid + + always: + - name: Delete tag value + delete_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid }}" + when: tag_value_uuid is defined + register: tag_value_deletion + + - name: Verify tag value is deleted + ansible.builtin.assert: + that: + - tag_value_deletion.api_response.status_code == 200 + when: tag_value_deletion.changed + + - name: Delete tag category + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + when: tag_category_uuid is defined + register: tag_deletion + + + - name: Verify tag category is deleted + ansible.builtin.assert: + that: + - tag_deletion.api_response.status_code == 200 + when: tag_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/get_timezones/tasks/main.yml b/tests/integration/targets/get_timezones/tasks/main.yml new file mode 100644 index 0000000..6ede3e5 --- /dev/null +++ b/tests/integration/targets/get_timezones/tasks/main.yml @@ -0,0 +1,13 @@ +--- +- name: Get all timezones + get_timezones: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: all_timezones + +- name: Verify timezones are received + ansible.builtin.assert: + that: + - all_timezones.api_response.status_code == 200 + - '"timezones" in all_timezones.api_response.data' + - "'Europe/Madrid' in (all_timezones.api_response.data.timezones | map(attribute='name') | list)" diff --git a/tests/integration/targets/launch_scan/tasks/main.yml b/tests/integration/targets/launch_scan/tasks/main.yml new file mode 100644 index 0000000..b05ad73 --- /dev/null +++ b/tests/integration/targets/launch_scan/tasks/main.yml @@ -0,0 +1,84 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "{{ agent_group_creation }}" + register: agent_group_creation_response + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == agent_group_creation ' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "{{ name_scan_creation }}" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - scan_creation.api_response.data.scan.name == name_scan_creation + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + - name: Launch the scan just created + launch_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + alt_targets: + - "1.11.11.111" + - "2.22.22.2" + - "3.33.3.3" + register: launch_response + + - name: Verify the launch scan luanch response is correct + ansible.builtin.assert: + that: + - launch_response.changed + - launch_response.api_response.status_code == 200 + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed + + - name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + when: scan_id_creation is defined + register: scan_deletion + + - name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + when: scan_deletion.changed diff --git a/tests/integration/targets/list_agent_exclusions/tasks/main.yml b/tests/integration/targets/list_agent_exclusions/tasks/main.yml new file mode 100644 index 0000000..da4256f --- /dev/null +++ b/tests/integration/targets/list_agent_exclusions/tasks/main.yml @@ -0,0 +1,93 @@ +- block: + - name: Create agent exclusion + create_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "bit_exception" + description: "description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-7-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: create_agent_exclusion_response + + + - name: Verify agent exclusion is created + ansible.builtin.assert: + that: + - create_agent_exclusion_response.api_response.status_code == 200 + - create_agent_exclusion_response.changed + + - name: Set fact of the exclusin id + set_fact: + exclusion_uuid: "{{ create_agent_exclusion_response.api_response.data.id }}" + + - name: Get agent exclusion details + get_agent_exclusion_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + register: exclusion_details + + + - name: Verify details are received + ansible.builtin.assert: + that: + - exclusion_details.api_response.status_code == 200 + - exclusion_details.api_response.data.id == create_agent_exclusion_response.api_response.data.id + + - name: List agent exclusions + list_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: list_agent_exclusion_response + + - name: List agent exclusions + ansible.builtin.assert: + that: + - '"exclusions" in list_agent_exclusion_response.api_response.data' + + - name: Update agent exclusion + update_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + name: "Updated exclusion name" + description: "Updated description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-07-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: update_agent_exclusion_response + + - name: Verify update was made + ansible.builtin.assert: + that: + - update_agent_exclusion_response.api_response.status_code == 200 + - update_agent_exclusion_response.changed + always: + - name: Delete exclusion + delete_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ create_agent_exclusion_response.api_response.data.id }}" + when: create_agent_exclusion_response.api_response.data.id is defined + register: exclusion_deletion + + - name: Verify deletion was made + ansible.builtin.assert: + that: + - exclusion_deletion.api_response.status_code == 200 + when: exclusion_deletion.changed diff --git a/tests/integration/targets/list_agent_filters/tasks/main.yml b/tests/integration/targets/list_agent_filters/tasks/main.yml new file mode 100644 index 0000000..714f982 --- /dev/null +++ b/tests/integration/targets/list_agent_filters/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List agent filters + list_agent_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - '"filters" in response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_agents/tasks/main.yml b/tests/integration/targets/list_agents/tasks/main.yml new file mode 100644 index 0000000..487116e --- /dev/null +++ b/tests/integration/targets/list_agents/tasks/main.yml @@ -0,0 +1,53 @@ +- name: List all agents + list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: all_agents + +- name: Verify agents resposne is correct + ansible.builtin.assert: + that: + - all_agents.api_response.status_code == 200 + - '"agents" in all_agents.api_response.data' + +- name: List all agents that in their ip contain 192.168. + list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + wildcard_text: 192.168. + wildcard_fields: ip + limit: 1 + register: agents_ip + +- name: Verify the are agents with 192.168 in their ip + ansible.builtin.assert: + that: + - agents_ip.api_response.status_code == 200 + - '"agents" in agents_ip.api_response.data' + - '"192.168." in agents_ip.api_response.data.agents[0].ip' + +- name: List agents with specified filters and wildcard using env variables + list_agents: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: core_version + operator: match + value: 10.4.4 + - type: platform + operator: eq + value: LINUX + - type: groups + operator: eq + value: 127110 + limit: 1 + register: multiple_filters_agents + +- name: Verify multiple filters + ansible.builtin.assert: + that: + - multiple_filters_agents.api_response.status_code == 200 + - '"agents" in multiple_filters_agents.api_response.data' + - 'multiple_filters_agents.api_response.data.agents[0].core_version == "10.4.4"' + - 'multiple_filters_agents.api_response.data.agents[0].platform == "LINUX"' + - 'multiple_filters_agents.api_response.data.agents[0].groups[0].id == 127110' diff --git a/tests/integration/targets/list_agents_by_group/tasks/main.yml b/tests/integration/targets/list_agents_by_group/tasks/main.yml new file mode 100644 index 0000000..5034010 --- /dev/null +++ b/tests/integration/targets/list_agents_by_group/tasks/main.yml @@ -0,0 +1,16 @@ +- name: List agents by group wiht enviroment creentials + list_agents_by_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + agent_group_id: "{{ agent_group_id }}" + filters: + - type: core_version + operator: match + value: "10.6.2" + register: agents_filtered + +- name: Verify agent group is well filtered + ansible.builtin.assert: + that: + - agents_filtered.api_response.status_code == 200 + - 'agents_filtered.api_response.data.agents[0].core_version == "10.6.2"' \ No newline at end of file diff --git a/tests/integration/targets/list_asset_filters/tasks/main.yml b/tests/integration/targets/list_asset_filters/tasks/main.yml new file mode 100644 index 0000000..122b926 --- /dev/null +++ b/tests/integration/targets/list_asset_filters/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List asset filters + list_asset_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - '"filters" in response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_asset_tag_filters/tasks/main.yml b/tests/integration/targets/list_asset_tag_filters/tasks/main.yml new file mode 100644 index 0000000..1962a28 --- /dev/null +++ b/tests/integration/targets/list_asset_tag_filters/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List tag asset filters + list_asset_tag_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: list_asset_filters + +- name: Verify filters are retuned + ansible.builtin.assert: + that: + - list_asset_filters.api_response.status_code == 200 + - '"filters" in list_asset_filters.api_response.data' diff --git a/tests/integration/targets/list_asset_vulnerabilities/tasks/main.yml b/tests/integration/targets/list_asset_vulnerabilities/tasks/main.yml new file mode 100644 index 0000000..b88dcfe --- /dev/null +++ b/tests/integration/targets/list_asset_vulnerabilities/tasks/main.yml @@ -0,0 +1,41 @@ +- name: Get all vulns from asset + list_asset_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "{{ asset_id_with_vulns }}" + register: asset_info_vulns + +- name: Verify that there are vulns of the asset + ansible.builtin.assert: + that: + - asset_info_vulns.api_response.status_code == 200 + - '"total_asset_count" in asset_info_vulns.api_response.data' + - 'asset_info_vulns.api_response.data.total_vulnerability_count > 0' + +- name: Get vulns from asset applying filters + list_asset_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "{{ asset_id_with_vulns }}" + filters: + - type: plugin.attributes.solution + operator: match + value: "Update the affected packages" + - type: plugin.attributes.cvss_base_score + operator: gte + value: "5" + - type: tracking.state + operator: eq + value: "Active" + register: filter_asset_vulns + +- name: Verify that there are vulns of the asset and returned data match the filters + ansible.builtin.assert: + that: + - filter_asset_vulns.api_response.status_code == 200 + - '"total_asset_count" in filter_asset_vulns.api_response.data' + - 'filter_asset_vulns.api_response.data.total_vulnerability_count > 0' + - 'filter_asset_vulns.api_response.data.vulnerabilities is defined' + - 'filter_asset_vulns.api_response.data.vulnerabilities | length > 0' + - 'filter_asset_vulns.api_response.data.vulnerabilities[0].cvss_base_score > 5' + - 'filter_asset_vulns.api_response.data.vulnerabilities[0].vulnerability_state == "Active"' diff --git a/tests/integration/targets/list_asset_vulnerabilities_for_plugin/tasks/main.yml b/tests/integration/targets/list_asset_vulnerabilities_for_plugin/tasks/main.yml new file mode 100644 index 0000000..9a49221 --- /dev/null +++ b/tests/integration/targets/list_asset_vulnerabilities_for_plugin/tasks/main.yml @@ -0,0 +1,33 @@ +- name: Get all vulns from asset + list_asset_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "{{ asset_to_add_and_remove_tags_uuid }}" + register: asset_info_vulns + +- name: Verify that there are vulns of the asset + ansible.builtin.assert: + that: + - asset_info_vulns.api_response.status_code == 200 + - '"total_asset_count" in asset_info_vulns.api_response.data' + - 'asset_info_vulns.api_response.data.total_vulnerability_count > 0' + +- name: Get the plugin from the asset vulns + set_fact: + plugin_from_vuls: "{{ asset_info_vulns.api_response.data.vulnerabilities[0].plugin_id }}" + + +- name: Get details of the plugin + list_asset_vulnerabilities_for_plugin: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_id: "{{ asset_to_add_and_remove_tags_uuid }}" + plugin_id: "{{ plugin_from_vuls }}" + register: list_asset_vulnerabilities_for_plugin_info + + +- name: Verify that there are vulns of the asset + ansible.builtin.assert: + that: + - list_asset_vulnerabilities_for_plugin_info.api_response.status_code == 200 + - '"outputs" in list_asset_vulnerabilities_for_plugin_info.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_asset_with_vulnerabilities/tasks/main.yml b/tests/integration/targets/list_asset_with_vulnerabilities/tasks/main.yml new file mode 100644 index 0000000..ff7aba2 --- /dev/null +++ b/tests/integration/targets/list_asset_with_vulnerabilities/tasks/main.yml @@ -0,0 +1,11 @@ +- name: Get all assets with their vulnerabilties + valkiriaaquatica.tenable.list_asset_with_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: all_assets_and_vulns + +- name: Verify all assets list is well recevied + ansible.builtin.assert: + that: + - all_assets_and_vulns.api_response.status_code == 200 + - '"assets" in all_assets_and_vulns.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_assets/tasks/main.yml b/tests/integration/targets/list_assets/tasks/main.yml new file mode 100644 index 0000000..532184e --- /dev/null +++ b/tests/integration/targets/list_assets/tasks/main.yml @@ -0,0 +1,35 @@ +- name: List assets with one filter + list_assets: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: aws_ec2_instance_type + operator: eq + value: "t2.small" + register: asset_with_two_filters + + +- name: Verify asset is received + ansible.builtin.assert: + that: + - asset_with_two_filters.api_response.status_code == 200 + - '"assets" in asset_with_two_filters.api_response.data' + +- name: Get an asset from aws instance id + list_assets: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: aws_ec2_instance_type + operator: eq + value: "t2.small" + - type: aws_availability_zone + operator: neq + value: "ap-northeast-2a" + register: asset_aws + +- name: Verify asset is received + ansible.builtin.assert: + that: + - asset_aws.api_response.status_code == 200 + - '"assets" in asset_aws.api_response.data' diff --git a/tests/integration/targets/list_assignable_scanners/tasks/main.yml b/tests/integration/targets/list_assignable_scanners/tasks/main.yml new file mode 100644 index 0000000..ce64097 --- /dev/null +++ b/tests/integration/targets/list_assignable_scanners/tasks/main.yml @@ -0,0 +1,12 @@ +- name: List assignable scanners + list_assignable_scanners: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "{{ network_uuid }}" + register: assignable_scanners + +- name: Verify network was well created + ansible.builtin.assert: + that: + - assignable_scanners.api_response.status_code == 200 + - '"scanners" in assignable_scanners.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_credential_filters/tasks/main.yml b/tests/integration/targets/list_credential_filters/tasks/main.yml new file mode 100644 index 0000000..b353d38 --- /dev/null +++ b/tests/integration/targets/list_credential_filters/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List credential filters + list_credential_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - '"filters" in response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_folders/tasks/main.yml b/tests/integration/targets/list_folders/tasks/main.yml new file mode 100644 index 0000000..d0bcefa --- /dev/null +++ b/tests/integration/targets/list_folders/tasks/main.yml @@ -0,0 +1,12 @@ +--- +- name: Get all folders + list_folders: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: all_folders + +- name: Verify folders are received + ansible.builtin.assert: + that: + - all_folders.api_response.status_code == 200 + - '"folders" in all_folders.api_response.data' diff --git a/tests/integration/targets/list_groups/tasks/main.yml b/tests/integration/targets/list_groups/tasks/main.yml new file mode 100644 index 0000000..a92813f --- /dev/null +++ b/tests/integration/targets/list_groups/tasks/main.yml @@ -0,0 +1,12 @@ +- name: List all groups + list_groups: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: all_groups + +- name: Verify all groups info are received + ansible.builtin.assert: + that: + - all_groups.api_response.status_code == 200 + - '"groups" in all_groups.api_response.data' + - '"container_uuid" in all_groups.api_response.data.groups[0]' \ No newline at end of file diff --git a/tests/integration/targets/list_networks/tasks/main.yml b/tests/integration/targets/list_networks/tasks/main.yml new file mode 100644 index 0000000..0113c3e --- /dev/null +++ b/tests/integration/targets/list_networks/tasks/main.yml @@ -0,0 +1,62 @@ +--- +- name: List all networks with any filters passing acces and secret key + list_networks: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: all_networks + +- name: Verify all networks list is well recevied + ansible.builtin.assert: + that: + - all_networks.api_response.status_code == 200 + - '"networks" in all_networks.api_response.data' + +- name: List network with name filter + list_networks: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: "name" + operator: "eq" + value: "{{ network_name }}" + include_deleted: false + register: filtered_network + +- name: Verificar la respuesta de redes filtradas + ansible.builtin.assert: + that: + - all_networks.api_response.status_code == 200 + - '"networks" in all_networks.api_response.data' + - 'filtered_network.api_response.data.networks[0].name == network_name' + +- name: Get Netowkr uuid + ansible.builtin.set_fact: + network_uuid: "{{ filtered_network.api_response.data.networks[0].uuid }}" + + +- name: Get network details of the network above + get_network_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "{{ network_uuid }}" + + +- name: Use two filters to get two networks + list_networks: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + filters: + - type: name + operator: eq + value: "{{ network_name }}" + - type: name + operator: eq + value: "{{ second_network_name }}" + + +- name: Verificar la respuesta de redes filtradas + ansible.builtin.assert: + that: + - all_networks.api_response.status_code == 200 + - '"networks" in all_networks.api_response.data' + - 'all_networks.api_response.data.networks[0].name == network_name' \ No newline at end of file diff --git a/tests/integration/targets/list_plugin_families/tasks/main.yml b/tests/integration/targets/list_plugin_families/tasks/main.yml new file mode 100644 index 0000000..7c8b74e --- /dev/null +++ b/tests/integration/targets/list_plugin_families/tasks/main.yml @@ -0,0 +1,25 @@ +- name: List all plugin families + list_plugin_families: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + all: True + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.changed == false + - response.failed == false + +- name: List not all plugin families + list_plugin_families: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + all: False + register: response_2 + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response_2.api_response.status_code == 200 + - '"families" in response_2.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_plugin_in_families_id/tasks/main.yml b/tests/integration/targets/list_plugin_in_families_id/tasks/main.yml new file mode 100644 index 0000000..8cf04b1 --- /dev/null +++ b/tests/integration/targets/list_plugin_in_families_id/tasks/main.yml @@ -0,0 +1,12 @@ +- name: List plugin in families + list_plugin_in_familiy_id: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + id: 56 + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - response.api_response.data.id == 56 \ No newline at end of file diff --git a/tests/integration/targets/list_plugin_outputs/tasks/main.yml b/tests/integration/targets/list_plugin_outputs/tasks/main.yml new file mode 100644 index 0000000..e8ae0d5 --- /dev/null +++ b/tests/integration/targets/list_plugin_outputs/tasks/main.yml @@ -0,0 +1,12 @@ +- name: Retrieve plugin details with specific filters + list_plugin_outputs: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + plugin_id: 56 + register: plugin_outputs + +- name: Verify plugin outputs are received + ansible.builtin.assert: + that: + - plugin_outputs.api_response.status_code == 200 + - '"outputs" in plugin_outputs.api_response.data' diff --git a/tests/integration/targets/list_plugins/tasks/main.yml b/tests/integration/targets/list_plugins/tasks/main.yml new file mode 100644 index 0000000..cc6236b --- /dev/null +++ b/tests/integration/targets/list_plugins/tasks/main.yml @@ -0,0 +1,29 @@ +- name: List one plugin + list_plugins: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + size: 1 + register: plugin_info + +- name: Verify size is received + ansible.builtin.assert: + that: + - plugin_info.api_response.status_code == 200 + - '"plugin_details" in plugin_info.api_response.data.data' + - 'plugin_info.api_response.data.size == 1' + + +- name: List plugins + list_plugins: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + page: 3 + register: plugin_info_2 + + +- name: Verify page is received + ansible.builtin.assert: + that: + - plugin_info_2.api_response.status_code == 200 + - '"plugin_details" in plugin_info_2.api_response.data.data' + - 'plugin_info_2.api_response.data.params.page == 3' \ No newline at end of file diff --git a/tests/integration/targets/list_plugins_in_family_name/tasks/main.yml b/tests/integration/targets/list_plugins_in_family_name/tasks/main.yml new file mode 100644 index 0000000..a346e5a --- /dev/null +++ b/tests/integration/targets/list_plugins_in_family_name/tasks/main.yml @@ -0,0 +1,12 @@ +- name: List plugin in families + list_plugins_in_family_name: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Windows : User management" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - response.changed \ No newline at end of file diff --git a/tests/integration/targets/list_policies/tasks/main.yml b/tests/integration/targets/list_policies/tasks/main.yml new file mode 100644 index 0000000..371f633 --- /dev/null +++ b/tests/integration/targets/list_policies/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List all policies in Tenable + list_policies: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response_policies + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response_policies.api_response.status_code == 200 + - '"policies" in response_policies.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_remediation_scans/tasks/main.yml b/tests/integration/targets/list_remediation_scans/tasks/main.yml new file mode 100644 index 0000000..ac530d5 --- /dev/null +++ b/tests/integration/targets/list_remediation_scans/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List remediation scans + list_remediation_scans: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - '"pagination" in response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_report_filters/tasks/main.yml b/tests/integration/targets/list_report_filters/tasks/main.yml new file mode 100644 index 0000000..36ff564 --- /dev/null +++ b/tests/integration/targets/list_report_filters/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List report filters + list_report_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - '"filters" in response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_scan_filters/tasks/main.yml b/tests/integration/targets/list_scan_filters/tasks/main.yml new file mode 100644 index 0000000..dcb439f --- /dev/null +++ b/tests/integration/targets/list_scan_filters/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List scan filters + list_scan_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - '"filters" in response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_scan_history_filters/tasks/main.yml b/tests/integration/targets/list_scan_history_filters/tasks/main.yml new file mode 100644 index 0000000..e4b86ee --- /dev/null +++ b/tests/integration/targets/list_scan_history_filters/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List scan history filters + list_scan_history_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - '"filters" in response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_scan_routes/tasks/main.yml b/tests/integration/targets/list_scan_routes/tasks/main.yml new file mode 100644 index 0000000..5755241 --- /dev/null +++ b/tests/integration/targets/list_scan_routes/tasks/main.yml @@ -0,0 +1,55 @@ +- block: + - name: Create a new scanner group with default type + create_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" + type: "load_balancing" + register: create_scanner_group_response + + - name: Verify the scanner group is well created + ansible.builtin.assert: + that: + - create_scanner_group_response.changed + + - name: Set fact of scanner grup + set_fact: + scanner_group_id: "{{ create_scanner_group_response.api_response.data.id }}" + + - name: Get details of a scanner group + list_scanner_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scanner_group_details_response + + - name: Verify details are received + ansible.builtin.assert: + that: + - list_scanner_group_details_response.api_response.data.id == scanner_group_id|int + + - name: List scanners within a scanner group + list_scan_routes: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scan_routes + + - name: Verify scan routes response + ansible.builtin.assert: + that: + - list_scan_routes.api_response.status_code == 200 + always: + - name: Delete a scanner group + delete_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + when: scanner_group_id is defined + register: delete_scanner_group_response + + - name: Verify the scanner group is deleted + ansible.builtin.assert: + that: + - delete_scanner_group_response.changed + when: delete_scanner_group_response.changed \ No newline at end of file diff --git a/tests/integration/targets/list_scanner_group_details/tasks/main.yml b/tests/integration/targets/list_scanner_group_details/tasks/main.yml new file mode 100644 index 0000000..f3773c8 --- /dev/null +++ b/tests/integration/targets/list_scanner_group_details/tasks/main.yml @@ -0,0 +1,43 @@ +- block: + - name: Create a new scanner group with default type + create_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" + type: "load_balancing" + register: create_scanner_group_response + + - name: Verify the scanner group is well created + ansible.builtin.assert: + that: + - create_scanner_group_response.changed + + - name: Set fact of scanner grup + set_fact: + scanner_group_id: "{{ create_scanner_group_response.api_response.data.id }}" + + - name: Get details of a scanner group + list_scanner_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scanner_group_details_response + + - name: Verify details are received + ansible.builtin.assert: + that: + - list_scanner_group_details_response.api_response.data.id == scanner_group_id|int + always: + - name: Delete a scanner group + delete_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + when: scanner_group_id is defined + register: delete_scanner_group_response + + - name: Verify the scanner group is deleted + ansible.builtin.assert: + that: + - delete_scanner_group_response.changed + when: delete_scanner_group_response.changed \ No newline at end of file diff --git a/tests/integration/targets/list_scanner_groups/tasks/main.yml b/tests/integration/targets/list_scanner_groups/tasks/main.yml new file mode 100644 index 0000000..0be87f5 --- /dev/null +++ b/tests/integration/targets/list_scanner_groups/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List scanner groups + list_scanner_groups: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: list_scanner_groups_response + +- name: Verify all scanners is well returned + ansible.builtin.assert: + that: + - list_scanner_groups_response.api_response.status_code == 200 + - '"scanner_pools" in list_scanner_groups_response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_scanners/tasks/main.yml b/tests/integration/targets/list_scanners/tasks/main.yml new file mode 100644 index 0000000..682e870 --- /dev/null +++ b/tests/integration/targets/list_scanners/tasks/main.yml @@ -0,0 +1,11 @@ +- name: list scanners + list_scanners: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: list_scanners + +- name: Verify all scanners is well returned + ansible.builtin.assert: + that: + - list_scanners.api_response.status_code == 200 + - '"scanners" in list_scanners.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_scanners_within_scanner_group/tasks/main.yml b/tests/integration/targets/list_scanners_within_scanner_group/tasks/main.yml new file mode 100644 index 0000000..fe8520b --- /dev/null +++ b/tests/integration/targets/list_scanners_within_scanner_group/tasks/main.yml @@ -0,0 +1,56 @@ +- block: + - name: Create a new scanner group with default type + create_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" + type: "load_balancing" + register: create_scanner_group_response + + - name: Verify the scanner group is well created + ansible.builtin.assert: + that: + - create_scanner_group_response.changed + + - name: Set fact of scanner grup + set_fact: + scanner_group_id: "{{ create_scanner_group_response.api_response.data.id }}" + + - name: Get details of a scanner group + list_scanner_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scanner_group_details_response + + - name: Verify details are received + ansible.builtin.assert: + that: + - list_scanner_group_details_response.api_response.data.id == scanner_group_id|int + + - name: List scanners within a scanner group + list_scanners_within_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scanners_within_scanner_group_response + + - name: Verify all within scanner group is well received + ansible.builtin.assert: + that: + - list_scanners_within_scanner_group_response.api_response.status_code == 200 + - '"scanners" in list_scanners_within_scanner_group_response.api_response.data' + always: + - name: Delete a scanner group + delete_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + when: scanner_group_id is defined + register: delete_scanner_group_response + + - name: Verify the scanner group is deleted + ansible.builtin.assert: + that: + - delete_scanner_group_response.changed + when: delete_scanner_group_response.changed \ No newline at end of file diff --git a/tests/integration/targets/list_scans/tasks/main.yml b/tests/integration/targets/list_scans/tasks/main.yml new file mode 100644 index 0000000..152ebb8 --- /dev/null +++ b/tests/integration/targets/list_scans/tasks/main.yml @@ -0,0 +1,56 @@ +- block: + - name: Create folder + create_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible-collection_folder" + register: create_folder_response + + - name: Verify creation was well + ansible.builtin.assert: + that: + - create_folder_response.api_response.status_code == 200 + - create_folder_response.changed + + - name: Get folder id + set_fact: + folder_id_creation: "{{ create_folder_response.api_response.data.id }}" + + - name: List all scans + list_scans: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: all_scans + + - name: Verify all scans is well received + ansible.builtin.assert: + that: + - all_scans.api_response.status_code == 200 + - '"scans" in all_scans.api_response.data' + + - name: List scan from folder + list_scans: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: "{{ folder_id_creation }}" + register: folder_scans + + - name: Verify all scans is well received + ansible.builtin.assert: + that: + - folder_scans.api_response.status_code == 200 + - '"folders" in folder_scans.api_response.data' + always: + - name: Delete the folder + delete_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: "{{ folder_id_creation }}" + when: folder_id_creation is defined + register: delete_folder_response + + - name: Verify delete was well + ansible.builtin.assert: + that: + - delete_folder_response.api_response.status_code == 200 + - delete_folder_response.changed diff --git a/tests/integration/targets/list_tag_categories/tasks/main.yml b/tests/integration/targets/list_tag_categories/tasks/main.yml new file mode 100644 index 0000000..705eb7e --- /dev/null +++ b/tests/integration/targets/list_tag_categories/tasks/main.yml @@ -0,0 +1,12 @@ +- name: List tag categories using enviroment creds + list_tag_categories: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + limit: 1 + register: tag_categories + +- name: Verify list categories is received and with limit 1 + ansible.builtin.assert: + that: + - tag_categories.api_response.status_code == 200 + - 'tag_categories.api_response.data.pagination.limit == 1' diff --git a/tests/integration/targets/list_tag_values/tasks/main.yml b/tests/integration/targets/list_tag_values/tasks/main.yml new file mode 100644 index 0000000..b1492e5 --- /dev/null +++ b/tests/integration/targets/list_tag_values/tasks/main.yml @@ -0,0 +1,35 @@ +- block: + - name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get if of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + always: + - name: Delete tag category + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + when: tag_category_uuid is defined + register: tag_deletion + + + - name: Verify tag is deleted + ansible.builtin.assert: + that: + - tag_deletion.api_response.status_code == 200 + when: tag_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/list_tags_for_an_asset/tasks/main.yml b/tests/integration/targets/list_tags_for_an_asset/tasks/main.yml new file mode 100644 index 0000000..a9551aa --- /dev/null +++ b/tests/integration/targets/list_tags_for_an_asset/tasks/main.yml @@ -0,0 +1,12 @@ +- name: List tags for an asset + list_tags_for_an_asset: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "{{ asset_uuid }}" + register: asset_tags + +- name: Verify list categories is received and with limit 1 + ansible.builtin.assert: + that: + - asset_tags.api_response.status_code == 200 + - 'asset_tags.api_response.data.tags[0].asset_uuid == asset_uuid' diff --git a/tests/integration/targets/list_templates/tasks/main.yml b/tests/integration/targets/list_templates/tasks/main.yml new file mode 100644 index 0000000..900df18 --- /dev/null +++ b/tests/integration/targets/list_templates/tasks/main.yml @@ -0,0 +1,39 @@ +- name: List scan templates + list_templates: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + type: "scan" + register: scan_templates + + +- name: Verify response + ansible.builtin.assert: + that: + - scan_templates.api_response.status_code == 200 + - '"templates" in scan_templates.api_response.data' + +- name: List policy templates + list_templates: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + type: "policy" + register: policy_templates + +- name: Verify response + ansible.builtin.assert: + that: + - policy_templates.api_response.status_code == 200 + - '"templates" in policy_templates.api_response.data' + +- name: List remediation templates + list_templates: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + type: "remediation" + register: remediation_templates + +- name: Verify response + ansible.builtin.assert: + that: + - remediation_templates.api_response.status_code == 200 + - '"templates" in remediation_templates.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/list_vulnerabilities/tasks/main.yml b/tests/integration/targets/list_vulnerabilities/tasks/main.yml new file mode 100644 index 0000000..5b282da --- /dev/null +++ b/tests/integration/targets/list_vulnerabilities/tasks/main.yml @@ -0,0 +1,37 @@ +- name: List vulnerabilities + list_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + date_range: 2 + filters: + - type: plugin.attributes.vpr.score + operator: eq + value: "5" + register: list_vulnerabilities_response + + +- name: Verify list vulns response + ansible.builtin.assert: + that: + - list_vulnerabilities_response.api_response.status_code == 200 + +- name: List vulnerabilities + list_vulnerabilities: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + date_range: 2 + + filters: + - type: plugin.attributes.vpr.score + operator: eq + value: "5" + - type: tracking.state + operator: neq + value: "Fixed" + register: list_vulnerabilities_response + + +- name: Verify list vulns response + ansible.builtin.assert: + that: + - list_vulnerabilities_response.api_response.status_code == 200 \ No newline at end of file diff --git a/tests/integration/targets/list_vulnerability_filters/tasks/main.yml b/tests/integration/targets/list_vulnerability_filters/tasks/main.yml new file mode 100644 index 0000000..a8ef7a9 --- /dev/null +++ b/tests/integration/targets/list_vulnerability_filters/tasks/main.yml @@ -0,0 +1,11 @@ +- name: List vulnerability filters + list_vulnerability_filters: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: response + +- name: Verify petition is well received + ansible.builtin.assert: + that: + - response.api_response.status_code == 200 + - '"filters" in response.api_response.data' \ No newline at end of file diff --git a/tests/integration/targets/move_assets/tasks/main.yml b/tests/integration/targets/move_assets/tasks/main.yml new file mode 100644 index 0000000..dee96ef --- /dev/null +++ b/tests/integration/targets/move_assets/tasks/main.yml @@ -0,0 +1,48 @@ +- name: Get asset details + get_asset_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "{{ asset_to_test_with_uuid }}" + register: asset_details + + +- name: Verify asset details is received + ansible.builtin.assert: + that: + - asset_details.api_response.status_code == 200 + - asset_details.api_response.data.id == asset_to_test_with_uuid + + +- name: Set value of actual network + set_fact: + actual_network_uuid: "{{ asset_details.api_response.data.network_id }}" + +- name: Move asset + move_assets: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + targets: "{{ asset_to_test_with_ip }}" + source: "{{ actual_network_uuid }}" + destination: "{{ network_id }}" + register: move_assets_change + +- name: Verify asset network changed + ansible.builtin.assert: + that: + - move_assets_change.api_response.status_code == 202 + - move_assets_change.changed + +- name: Get asset details + get_asset_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + asset_uuid: "{{ asset_to_test_with_uuid }}" + register: asset_details_two + + +- name: Verify asset details is received + ansible.builtin.assert: + that: + - asset_details_two.api_response.status_code == 200 + - asset_details_two.api_response.data.id == asset_to_test_with_uuid + - asset_details_two.api_response.data.network_id[0] == network_id \ No newline at end of file diff --git a/tests/integration/targets/rename_agent/tasks/main.yml b/tests/integration/targets/rename_agent/tasks/main.yml new file mode 100644 index 0000000..83fedca --- /dev/null +++ b/tests/integration/targets/rename_agent/tasks/main.yml @@ -0,0 +1,14 @@ +- name: Rename agent + valkiriaaquatica.tenable.rename_agent: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + agent_id: "{{ agent_id }}" + name: "new_name" + register: rename_agent + +- name: Verify rename agent was succesfull + ansible.builtin.assert: + that: + - rename_agent.api_response.status_code == 200 + - rename_agent.changed + - 'rename_agent.api_response.data.id == {{ agent_id }}' \ No newline at end of file diff --git a/tests/integration/targets/rename_folder/tasks/main.yml b/tests/integration/targets/rename_folder/tasks/main.yml new file mode 100644 index 0000000..37ba017 --- /dev/null +++ b/tests/integration/targets/rename_folder/tasks/main.yml @@ -0,0 +1,45 @@ +- block: + - name: Create folder + create_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_folder" + register: create_folder_response + + - name: Verify creation was well + ansible.builtin.assert: + that: + - create_folder_response.api_response.status_code == 200 + - create_folder_response.changed + + - name: Get folder id + set_fact: + folder_id_creation: "{{ create_folder_response.api_response.data.id }}" + + - name: Rename the folder + rename_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: "{{ folder_id_creation }}" + name: "i_am_new_name" + register: update_folder_name + + - name: Verify delete was well + ansible.builtin.assert: + that: + - update_folder_name.api_response.status_code == 200 + - update_folder_name.changed + always: + - name: Delete the folder + delete_folder: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + folder_id: "{{ folder_id_creation }}" + when: folder_id_creation is defined + register: delete_folder_response + + - name: Verify delete was well + ansible.builtin.assert: + that: + - delete_folder_response.api_response.status_code == 200 + when: delete_folder_response.changed \ No newline at end of file diff --git a/tests/integration/targets/stop_scan/tasks/main.yml b/tests/integration/targets/stop_scan/tasks/main.yml new file mode 100644 index 0000000..a7a8023 --- /dev/null +++ b/tests/integration/targets/stop_scan/tasks/main.yml @@ -0,0 +1,100 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "{{ agent_group_creation }}" + register: agent_group_creation_response + + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == agent_group_creation ' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "{{ name_scan_creation }}" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - scan_creation.api_response.data.scan.name == name_scan_creation + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + + - name: Launch the scan just created + launch_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + alt_targets: + - "1.11.11.111" + - "2.22.22.2" + - "3.33.3.3" + register: launch_response + + - name: Verify the launch scan luanch response is correct + ansible.builtin.assert: + that: + - launch_response.changed + - launch_response.api_response.status_code == 200 + + - name: Stop the scan + stop_scan: + scan_id: "{{ scan_id_creation }}" + register: stop_scan_response + + + - name: Verify the stop scan response is correct + ansible.builtin.assert: + that: + - stop_scan_response.changed + - stop_scan_response.api_response.status_code == 200 + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed + + - name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + when: scan_id_creation is defined + register: scan_deletion + + - name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + when: scan_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/update_agent_exclusion/tasks/main.yml b/tests/integration/targets/update_agent_exclusion/tasks/main.yml new file mode 100644 index 0000000..ad2d085 --- /dev/null +++ b/tests/integration/targets/update_agent_exclusion/tasks/main.yml @@ -0,0 +1,95 @@ +- block: + - name: Create agent exclusion + create_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "bit_exception" + description: "description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-7-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: create_agent_exclusion_response + + + - name: Verify agent exclusion is created + ansible.builtin.assert: + that: + - create_agent_exclusion_response.api_response.status_code == 200 + - create_agent_exclusion_response.changed + + - name: Set fact of the exclusin id + set_fact: + exclusion_uuid: "{{ create_agent_exclusion_response.api_response.data.id }}" + + - name: Get agent exclusion details + get_agent_exclusion_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + register: exclusion_details + + + - name: Verify details are received + ansible.builtin.assert: + that: + - exclusion_details.api_response.status_code == 200 + - exclusion_details.api_response.data.id == create_agent_exclusion_response.api_response.data.id + + - name: List agent exclusions + list_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + register: list_agent_exclusion_response + + - name: List agent exclusions + ansible.builtin.assert: + that: + - '"exclusions" in list_agent_exclusion_response.api_response.data' + + + - name: Update agent exclusion + update_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_uuid }}" + name: "Updated exclusion name" + description: "Updated description" + schedule: + enabled: true + starttime: "2024-06-01 00:00:00" + endtime: "2024-07-07 23:59:59" + timezone: "US/Pacific" + rrules: + freq: "ONETIME" + interval: 1 + byweekday: "SU" + bymonthday: 1 + register: update_agent_exclusion_response + + - name: Verify update was made + ansible.builtin.assert: + that: + - update_agent_exclusion_response.api_response.status_code == 200 + - update_agent_exclusion_response.changed + + always: + - name: Delete exclusion + delete_agent_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ create_agent_exclusion_response.api_response.data.id }}" + when: create_agent_exclusion_response.api_response.data.id is defined + register: exclusion_deletion + + - name: Verify deletion was made + ansible.builtin.assert: + that: + - exclusion_deletion.api_response.status_code == 200 + when: exclusion_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/update_agent_group_name/tasks/main.yml b/tests/integration/targets/update_agent_group_name/tasks/main.yml new file mode 100644 index 0000000..03d5b57 --- /dev/null +++ b/tests/integration/targets/update_agent_group_name/tasks/main.yml @@ -0,0 +1,29 @@ +- name: Update the name of an agent + valkiriaaquatica.tenable.update_agent_group_name: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id }}" + name: "{{ not_good_group_name }}" + register: agent_group_rename + +- name: Verify agent group name is well changed + ansible.builtin.assert: + that: + - agent_group_rename.api_response.status_code == 200 + - agent_group_rename.api_response.data.id == agent_group_id + - agent_group_rename.api_response.data.name == not_good_group_name + +- name: Reveoke agent group name to the good value + valkiriaaquatica.tenable.update_agent_group_name: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id }}" + name: "{{ good_group_name }}" + register: agent_group_rename + +- name: Verify agent group name is well changed + ansible.builtin.assert: + that: + - agent_group_rename.api_response.status_code == 200 + - agent_group_rename.api_response.data.id == agent_group_id + - agent_group_rename.api_response.data.name == good_group_name \ No newline at end of file diff --git a/tests/integration/targets/update_an_exclusion/tasks/main.yml b/tests/integration/targets/update_an_exclusion/tasks/main.yml new file mode 100644 index 0000000..562017c --- /dev/null +++ b/tests/integration/targets/update_an_exclusion/tasks/main.yml @@ -0,0 +1,66 @@ +- block: + - name: Create a exclusion + create_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "exception_test_name" + members: "192.168.13.99, 192.168.18.100" + schedule: + enabled: true + starttime: "2021-04-01 09:00:00" + endtime: "2021-04-01 17:00:00" + timezone: "America/New_York" + rrules: + freq: "WEEKLY" + interval: 1 + byweekday: "MO,TU,WE,TH,FR" + #network_id: "{{ network_uuid_test }}" + register: exclusion_creation + + - name: Verify exclusion was well created + ansible.builtin.assert: + that: + - exclusion_creation.api_response.status_code == 200 + - exclusion_creation.changed + - 'exclusion_creation.api_response.data.name == "exception_test_name"' + - exclusion_creation.api_response.data.schedule.enabled == true + - exclusion_creation.api_response.data.schedule.endtime == "2021-04-01 17:00:00" + - exclusion_creation.api_response.data.schedule.rrules.freq == "WEEKLY" + - exclusion_creation.api_response.data.schedule.rrules.byweekday == "MO,TU,WE,TH,FR" + - exclusion_creation.api_response.data.schedule.timezone == "America/New_York" + + # remove the exclusion + - name: Get exclusion id + ansible.builtin.set_fact: + exclusion_id_created: "{{ exclusion_creation.api_response.data.id }}" + + - name: Update the exclusion + update_an_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_id_created }}" + name: "new_name" + register: update_exclusion + + - name: Verify the exclusion was changed + ansible.builtin.assert: + that: + - update_exclusion.api_response.status_code == 200 + - update_exclusion.changed + - update_exclusion.api_response.data.name == "new_name" + + always: + - name: Delete the exclusion + delete_an_exclusion: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + exclusion_id: "{{ exclusion_id_created }}" + when: exclusion_id_created is defined + register: delete_exclusion + + + - name: Verify exclusion was deleted + ansible.builtin.assert: + that: + - delete_exclusion.api_response.status_code == 200 + when: delete_exclusion.changed \ No newline at end of file diff --git a/tests/integration/targets/update_network/tasks/main.yml b/tests/integration/targets/update_network/tasks/main.yml new file mode 100644 index 0000000..920a0fe --- /dev/null +++ b/tests/integration/targets/update_network/tasks/main.yml @@ -0,0 +1,49 @@ +- name: Create a network + create_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_network" + description: "this is the descritpion" + assets_ttl_days: 50 + register: network_creation + +- name: Verify network was well created + ansible.builtin.assert: + that: + - network_creation.api_response.status_code == 200 + - network_creation.changed + - network_creation.api_response.data.assets_ttl_days == 50 + - network_creation.api_response.data.description == "this is the descritpion" + - network_creation.api_response.data.name == "ansible_collection_network" + +- name: Get network uuid + ansible.builtin.set_fact: + network_creation_uuid: "{{ network_creation.api_response.data.uuid }}" + +- name: Update a network + valkiriaaquatica.tenable.update_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "{{ network_creation_uuid }}" + name: "new_network_name" + register: udpate_network + +- name: Verify network was updated + ansible.builtin.assert: + that: + - udpate_network.api_response.status_code == 200 + - udpate_network.changed + - udpate_network.api_response.data.name == "new_network_name" + +- name: Delete a network + delete_network: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + network_id: "{{ network_creation_uuid }}" + register: network_deleted + +- name: Verify network was deleted + ansible.builtin.assert: + that: + - network_deleted.api_response.status_code == 200 + - network_deleted.changed \ No newline at end of file diff --git a/tests/integration/targets/update_scan/tasks/main.yml b/tests/integration/targets/update_scan/tasks/main.yml new file mode 100644 index 0000000..ba7dfb6 --- /dev/null +++ b/tests/integration/targets/update_scan/tasks/main.yml @@ -0,0 +1,101 @@ +- block: + - name: Create agent group + create_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "agent_group_creation" + register: agent_group_creation_response + + + - name: Verify the group is well created + ansible.builtin.assert: + that: + - agent_group_creation_response.api_response.status_code == 200 + - agent_group_creation_response.changed + - 'agent_group_creation_response.api_response.data.name == "agent_group_creation"' + + - name: Get agent id that was just created + set_fact: + agent_group_id_created: "{{ agent_group_creation_response.api_response.data.uuid }}" + + + - name: Create a scan + create_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "name_scan_creation" + agent_group_id: "{{ agent_group_id_created }}" + register: scan_creation + + + - name: Verify the scan is well created + ansible.builtin.assert: + that: + - scan_creation.api_response.status_code == 200 + - scan_creation.changed + - 'scan_creation.api_response.data.scan.name == "name_scan_creation"' + + - name: Get scan id that was just created + set_fact: + scan_id_creation: "{{ scan_creation.api_response.data.scan.id }}" + + + - name: Update the scan values + update_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + uuid: "{{ template_scan_uuid }}" + settings: + name: "i_am_the_new_name" + plugin_configurations: + - plugin_family_name: "Red Hat Local Security Checks" + plugins: + - plugin_id: "79798" + status: "enabled" + - plugin_id: "79799" + status: "disabled" + + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + - agent_group_deletation.changed + always: + - name: Delete agent group that was just created + valkiriaaquatica.tenable.delete_agent_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ agent_group_id_created }}" + when: agent_group_id_created is defined + register: agent_group_deletation + + - name: Verify the group is deleted + ansible.builtin.assert: + that: + - agent_group_deletation.api_response.status_code == 200 + when: agent_group_deletation.changed + + + - name: Delete the scan + delete_scan: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + scan_id: "{{ scan_id_creation }}" + when: scan_id_creation is defined + register: scan_deletion + + - name: Verify the scan is deleted + ansible.builtin.assert: + that: + - scan_deletion.api_response.status_code == 200 + when: scan_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/update_scan_routes/tasks/main.yml b/tests/integration/targets/update_scan_routes/tasks/main.yml new file mode 100644 index 0000000..12ddbb3 --- /dev/null +++ b/tests/integration/targets/update_scan_routes/tasks/main.yml @@ -0,0 +1,58 @@ +- block: + - name: Create a new scanner group with default type + create_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" + type: "load_balancing" + register: create_scanner_group_response + + - name: Verify the scanner group is well created + ansible.builtin.assert: + that: + - create_scanner_group_response.changed + + - name: Set fact of scanner grup + set_fact: + scanner_group_id: "{{ create_scanner_group_response.api_response.data.id }}" + + - name: Get details of a scanner group + list_scanner_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scanner_group_details_response + + - name: Verify details are received + ansible.builtin.assert: + that: + - list_scanner_group_details_response.api_response.data.id == scanner_group_id|int + + - name: Update scan routes for a scanner group + update_scan_routes: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + routes: + - "example.com" + register: update_scan_routes_response + + - name: Verify scan routes response + ansible.builtin.assert: + that: + - update_scan_routes_response.api_response.status_code == 200 + - update_scan_routes_response.changed + always: + - name: Delete a scanner group + delete_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + when: scanner_group_id is defined + register: delete_scanner_group_response + + - name: Verify the scanner group is deleted + ansible.builtin.assert: + that: + - delete_scanner_group_response.changed + when: delete_scanner_group_response.changed \ No newline at end of file diff --git a/tests/integration/targets/update_scanner_group/tasks/main.yml b/tests/integration/targets/update_scanner_group/tasks/main.yml new file mode 100644 index 0000000..6e54aa8 --- /dev/null +++ b/tests/integration/targets/update_scanner_group/tasks/main.yml @@ -0,0 +1,57 @@ +- block: + - name: Create a new scanner group with default type + create_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "Example Group" + type: "load_balancing" + register: create_scanner_group_response + + - name: Verify the scanner group is well created + ansible.builtin.assert: + that: + - create_scanner_group_response.changed + + - name: Set fact of scanner grup + set_fact: + scanner_group_id: "{{ create_scanner_group_response.api_response.data.id }}" + + - name: Get details of a scanner group + list_scanner_group_details: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + register: list_scanner_group_details_response + + - name: Verify details are received + ansible.builtin.assert: + that: + - list_scanner_group_details_response.api_response.data.id == scanner_group_id|int + + - name: Update the name of the scanner group + update_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + name: "New Group Name" + register: update_scanner_group_response + + + - name: Verify the scanner group is well updated + ansible.builtin.assert: + that: + - update_scanner_group_response.changed + always: + - name: Delete a scanner group + delete_scanner_group: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + group_id: "{{ scanner_group_id }}" + when: scanner_group_id is defined + register: delete_scanner_group_response + + - name: Verify the scanner group is deleted + ansible.builtin.assert: + that: + - delete_scanner_group_response.changed + when: delete_scanner_group_response.changed \ No newline at end of file diff --git a/tests/integration/targets/update_tag_category/tasks/main.yml b/tests/integration/targets/update_tag_category/tasks/main.yml new file mode 100644 index 0000000..d21a69a --- /dev/null +++ b/tests/integration/targets/update_tag_category/tasks/main.yml @@ -0,0 +1,49 @@ +- block: + - name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get if of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + - name: Update tag category + update_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + name: "i_am_the_new_name" + description: "i am the new description" + register: update_tag_category + + - name: Verify tag is created + ansible.builtin.assert: + that: + - update_tag_category.api_response.status_code == 200 + - update_tag_category.changed + - 'update_tag_category.api_response.data.name == "i_am_the_new_name"' + always: + - name: Delete tag category + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + when: tag_category_uuid is defined + register: tag_deletion + + + - name: Verify tag is deleted + ansible.builtin.assert: + that: + - tag_deletion.api_response.status_code == 200 + when: tag_deletion.changed diff --git a/tests/integration/targets/update_tag_value/tasks/main.yml b/tests/integration/targets/update_tag_value/tasks/main.yml new file mode 100644 index 0000000..33e6dad --- /dev/null +++ b/tests/integration/targets/update_tag_value/tasks/main.yml @@ -0,0 +1,128 @@ +- block: + - name: Create tag ceategory + create_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + name: "ansible_collection_tag_category" + register: tag_category_creation_response + + + - name: Verify tag is created + ansible.builtin.assert: + that: + - tag_category_creation_response.api_response.status_code == 200 + - tag_category_creation_response.changed + - 'tag_category_creation_response.api_response.data.name == "ansible_collection_tag_category"' + + - name: Get id of the tag category + set_fact: + tag_category_uuid: "{{ tag_category_creation_response.api_response.data.uuid }}" + + - name: Get name of the tag category + set_fact: + tag_category_name: "{{ tag_category_creation_response.api_response.data.name }}" + + - name: Create tag value + create_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value: "tag_value" + category_name: "{{ tag_category_name }}" + category_uuid: "{{ tag_category_uuid }}" + register: tag_value_creation + + - name: Verify tag value is created + ansible.builtin.assert: + that: + - tag_value_creation.api_response.status_code == 200 + - tag_value_creation.changed + - tag_value_creation.api_response.data.category_uuid == tag_category_uuid + + - name: Get the uuid of the tag value just created + set_fact: + tag_value_uuid: "{{ tag_value_creation.api_response.data.uuid }}" + + - name: Update tag value # no filters applied in case no isntances available + update_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid }}" + value: "this_is_the_new_value" + register: tag_value_update + + - name: Verify update tag value was well done + ansible.builtin.assert: + that: + - tag_value_update.api_response.status_code == 200 + - tag_value_update.changed + - tag_value_update.api_response.data.category_uuid == tag_category_uuid + + + - name: Delete tag value + delete_tag_value: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + value_uuid: "{{ tag_value_uuid }}" + when: tag_value_uuid + register: tag_value_deletion + + - name: Verify tag value is deleted + ansible.builtin.assert: + that: + - tag_value_deletion.api_response.status_code == 200 + when: tag_value_deletion.changed + + # second value to proof filters + #- name: Create tag value + # create_tag_value: + # access_key: "{{ tenable_access_key }}" + # secret_key: "{{ tenable_secret_key }}" + # value: "tag_value_2" + #category_name: "{{ tag_category_name }}" + #filters: + # asset: + # and: + # - field: aws_ec2_name + # operator: eq + # value: "asset" + #register: tag_value_creation_2 + + #- name: Verify tag value is created + # ansible.builtin.assert: + # that: + # - tag_value_creation_2.api_response.status_code == 200 + # - tag_value_creation_2.changed + # - tag_value_creation_2.api_response.data.category_uuid == tag_category_uuid + + #- name: Get name of the tag uuid of the tag value just created + # set_fact: + # tag_value_uuid_2: "{{ tag_value_creation_2.api_response.data.uuid }}" + + always: + #- name: Delete tag value + # delete_tag_value: + # access_key: "{{ tenable_access_key }}" + # secret_key: "{{ tenable_secret_key }}" + #value_uuid: "{{ tag_value_uuid_2 }}" + #when: tag_value_uuid_2 is defined + #register: tag_value_deletion_2 + + #- name: Verify tag value is deleted + # ansible.builtin.assert: + # that: + # - tag_value_deletion_2.api_response.status_code == 200 + #when: tag_value_deletion_2.changed + + - name: Delete tag category + delete_tag_category: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + category_uuid: "{{ tag_category_uuid }}" + when: tag_category_uuid is defined + register: tag_deletion + + - name: Verify tag category is deleted + ansible.builtin.assert: + that: + - tag_deletion.api_response.status_code == 200 + when: tag_deletion.changed \ No newline at end of file diff --git a/tests/integration/targets/upload_file/tasks/main.yml b/tests/integration/targets/upload_file/tasks/main.yml new file mode 100644 index 0000000..43578bc --- /dev/null +++ b/tests/integration/targets/upload_file/tasks/main.yml @@ -0,0 +1,24 @@ +- name: Create file to upload + ansible.builtin.copy: + dest: "/tmp/hosts_to_upload.txt" + content: "1.1.1.1" + mode: '0644' + register: file_creation + +- name: Verify file creation + ansible.builtin.assert: + that: + - file_creation.changed + +- name: Upload file to Tenable + upload_file: + access_key: "{{ tenable_access_key }}" + secret_key: "{{ tenable_secret_key }}" + file_path: "/tmp/hosts_to_upload.txt" + register: upload_file + +- name: Verify file was uploades + ansible.builtin.assert: + that: + - upload_file.changed + - upload_file.api_response.status_code == 200 diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt new file mode 100644 index 0000000..c19a23c --- /dev/null +++ b/tests/sanity/ignore-2.15.txt @@ -0,0 +1 @@ +plugins/inventory/tenable.py yamllint:unparsable-with-libyaml # bug in ansible-test - https://github.com/ansible/ansible/issues/82353 \ No newline at end of file diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt new file mode 100644 index 0000000..c19a23c --- /dev/null +++ b/tests/sanity/ignore-2.16.txt @@ -0,0 +1 @@ +plugins/inventory/tenable.py yamllint:unparsable-with-libyaml # bug in ansible-test - https://github.com/ansible/ansible/issues/82353 \ No newline at end of file diff --git a/tests/unit/constants.py b/tests/unit/constants.py new file mode 100644 index 0000000..4e9c7b7 --- /dev/null +++ b/tests/unit/constants.py @@ -0,0 +1,11 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +BASE_MODULE_PATH = "ansible_collections.valkiriaaquatica.tenable.plugins.modules." +BASE_UTILS_PATH = "ansible_collections.valkiriaaquatica.tenable.plugins.module_utils." diff --git a/tests/unit/plugins/inventory/test_tenable.py b/tests/unit/plugins/inventory/test_tenable.py new file mode 100644 index 0000000..78e887f --- /dev/null +++ b/tests/unit/plugins/inventory/test_tenable.py @@ -0,0 +1,114 @@ +from unittest.mock import MagicMock +from unittest.mock import patch + +import pytest + +mock_assets = [ + { + "id": "asset1", + "hostname": "host1.example.com", + "fqdn": "host1.example.com", + "agent_name": "agent1", + "operating_system": ["OS1"], + }, + { + "id": "asset2", + "hostname": "host2.example.com", + "fqdn": "host2.example.com", + "agent_name": "agent2", + "operating_system": ["OS2"], + }, +] + + +@pytest.fixture +def inventory_module(): + from ansible_collections.valkiriaaquatica.tenable.plugins.inventory.tenable import InventoryModule + + return InventoryModule() + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.inventory.tenable.init_tenable_api") +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.inventory.tenable.InventoryModule.fetch_assets") +def test_parse(mock_fetch_assets, mock_init_api, inventory_module): + """Test the parse function of the InventoryModule.""" + mock_api_client = MagicMock() + mock_init_api.return_value = mock_api_client + mock_fetch_assets.return_value = mock_assets + + mock_inventory = MagicMock() + mock_loader = MagicMock() + path = "test_path" + + inventory_module._read_config_data = MagicMock() + inventory_module.get_option = MagicMock( + side_effect=lambda key, default=None: { + "access_key": "test_access_key", + "secret_key": "test_secret_key", + "include_filters": [], + "filter_search_type": "", + "all_fields": "", + "full_info": False, + "date_range": 30, + "hostname_sources": ["hostname", "fqdn", "agent_name"], + "hostname_prefix": "", + "hostname_suffix": "", + "hostname_separator": "", + "cache": False, + "groups": {}, + "compose": {}, + "keyed_groups": {}, + "strict": False, + }[key] + ) + + inventory_module.parse(mock_inventory, mock_loader, path) + mock_init_api.assert_called_once_with(access_key="test_access_key", secret_key="test_secret_key") + mock_fetch_assets.assert_called_once() + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.inventory.tenable.init_tenable_api") +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.inventory.tenable.InventoryModule.fetch_assets") +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.inventory.tenable.InventoryModule.fetch_asset_details") +def test_full_info(mock_fetch_asset_details, mock_fetch_assets, mock_init_api, inventory_module): + """Test full information fetching for assets.""" + mock_api_client = MagicMock() + mock_init_api.return_value = mock_api_client + mock_fetch_assets.return_value = mock_assets + + detailed_asset_1 = {**mock_assets[0], "details": "full details for asset 1"} + detailed_asset_2 = {**mock_assets[1], "details": "full details for asset 2"} + + mock_fetch_asset_details.side_effect = [detailed_asset_1, detailed_asset_2] + + mock_inventory = MagicMock() + mock_loader = MagicMock() + path = "test_path" + + inventory_module._read_config_data = MagicMock() + inventory_module.get_option = MagicMock( + side_effect=lambda key, default=None: { + "access_key": "test_access_key", + "secret_key": "test_secret_key", + "include_filters": [], + "filter_search_type": "", + "all_fields": "", + "full_info": True, + "date_range": 30, + "hostname_sources": ["hostname", "fqdn", "agent_name"], + "hostname_prefix": "", + "hostname_suffix": "", + "hostname_separator": "", + "cache": False, + "groups": {}, + "compose": {}, + "keyed_groups": {}, + "strict": False, + }[key] + ) + + inventory_module.parse(mock_inventory, mock_loader, path) + + mock_init_api.assert_called_once_with(access_key="test_access_key", secret_key="test_secret_key") + mock_fetch_assets.assert_called_once() + assert mock_fetch_asset_details.call_count == len(mock_assets) diff --git a/tests/unit/plugins/module_utils/test_api.py b/tests/unit/plugins/module_utils/test_api.py new file mode 100644 index 0000000..6c47c6e --- /dev/null +++ b/tests/unit/plugins/module_utils/test_api.py @@ -0,0 +1,133 @@ +import json +from unittest.mock import MagicMock +from unittest.mock import patch +from urllib.error import HTTPError +from urllib.error import URLError + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api import TenableAPI +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import AuthenticationError +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import BadRequestError +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import TenableAPIError +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import UnexpectedAPIResponse + + +@pytest.fixture +def module(): + return MagicMock() + + +def test_tenable_api_initialization(module): + module.params = {"access_key": "test_access_key", "secret_key": "test_secret_key"} + api = TenableAPI(module) + assert api.base_url == "https://cloud.tenable.com" + assert api.headers["X-ApiKeys"] == "accessKey=test_access_key;secretKey=test_secret_key" + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api.Request.open") +def test_tenable_api_request(mock_open, module): + mock_response = MagicMock() + mock_response.read.return_value = json.dumps({"result": "success"}).encode("utf-8") + mock_response.getcode.return_value = 200 + mock_open.return_value = mock_response + + module.params = {"access_key": "test_access_key", "secret_key": "test_secret_key"} + api = TenableAPI(module) + response = api.request("GET", "test_endpoint", params={"param1": "value1"}) + + mock_open.assert_called_once_with( + method="GET", + url="https://cloud.tenable.com/test_endpoint?param1=value1", + headers={"Accept": "application/json", "X-ApiKeys": "accessKey=test_access_key;secretKey=test_secret_key"}, + data=None, + ) + assert response == {"status_code": 200, "data": {"result": "success"}} + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api.Request.open") +def test_tenable_api_request_with_data(mock_open, module): + mock_response = MagicMock() + mock_response.read.return_value = json.dumps({"result": "success"}).encode("utf-8") + mock_response.getcode.return_value = 200 + mock_open.return_value = mock_response + + module.params = {"access_key": "test_access_key", "secret_key": "test_secret_key"} + api = TenableAPI(module) + response = api.request("POST", "test_endpoint", data={"key": "value"}) + + mock_open.assert_called_once_with( + method="POST", + url="https://cloud.tenable.com/test_endpoint", + headers={ + "Accept": "application/json", + "X-ApiKeys": "accessKey=test_access_key;secretKey=test_secret_key", + "Content-Type": "application/json", + }, + data=json.dumps({"key": "value"}).encode("utf-8"), + ) + assert response == {"status_code": 200, "data": {"result": "success"}} + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api.Request.open") +def test_tenable_api_request_http_error(mock_open, module): + mock_error_fp = MagicMock() + mock_error_fp.read.return_value = b"Bad Request" + mock_open.side_effect = HTTPError(url=None, code=400, msg="Bad Request", hdrs=None, fp=mock_error_fp) + + module.params = {"access_key": "test_access_key", "secret_key": "test_secret_key"} + api = TenableAPI(module) + with pytest.raises(BadRequestError) as excinfo: + api.request("GET", "test_endpoint") + assert "Bad Request" in str(excinfo.value) + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api.Request.open") +def test_tenable_api_request_auth_error(mock_open, module): + mock_error_fp = MagicMock() + mock_error_fp.read.return_value = b"Unauthorized" + mock_open.side_effect = HTTPError(url=None, code=401, msg="Unauthorized", hdrs=None, fp=mock_error_fp) + + module.params = {"access_key": "test_access_key", "secret_key": "test_secret_key"} + api = TenableAPI(module) + with pytest.raises(AuthenticationError) as excinfo: + api.request("GET", "test_endpoint") + assert "Authentication failure. Please check your API keys." in str(excinfo.value) + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api.Request.open") +def test_tenable_api_request_unexpected_error(mock_open, module): + mock_error_fp = MagicMock() + mock_error_fp.read.return_value = b"Server Error" + mock_open.side_effect = HTTPError(url=None, code=500, msg="Server Error", hdrs=None, fp=mock_error_fp) + + module.params = {"access_key": "test_access_key", "secret_key": "test_secret_key"} + api = TenableAPI(module) + with pytest.raises(UnexpectedAPIResponse) as excinfo: + api.request("GET", "test_endpoint") + assert "500" in str(excinfo.value) + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api.Request.open") +def test_tenable_api_request_url_error(mock_open, module): + mock_open.side_effect = URLError("URL Error") + + module.params = {"access_key": "test_access_key", "secret_key": "test_secret_key"} + api = TenableAPI(module) + with pytest.raises(TenableAPIError) as excinfo: + api.request("GET", "test_endpoint") + assert "URL error occurred" in str(excinfo.value) + + +@patch("ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.api.Request.open") +def test_tenable_api_request_timeout_error(mock_open, module): + mock_open.side_effect = TimeoutError("Request timed out") + + module.params = {"access_key": "test_access_key", "secret_key": "test_secret_key"} + api = TenableAPI(module) + with pytest.raises(TenableAPIError) as excinfo: + api.request("GET", "test_endpoint") + assert "Request timed out" in str(excinfo.value) + + +if __name__ == "__main__": + pytest.main() diff --git a/tests/unit/plugins/module_utils/test_arguments.py b/tests/unit/plugins/module_utils/test_arguments.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/plugins/module_utils/test_exceptions.py b/tests/unit/plugins/module_utils/test_exceptions.py new file mode 100644 index 0000000..dd64d5d --- /dev/null +++ b/tests/unit/plugins/module_utils/test_exceptions.py @@ -0,0 +1,41 @@ +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import AuthenticationError +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import BadRequestError +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import TenableAPIError +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.exceptions import UnexpectedAPIResponse + + +def test_tenable_api_error(): + """Test para la excepción TenableAPIError""" + error = TenableAPIError("Error message", 500) + assert str(error) == "Error message" + assert error.status_code == 500 + + +def test_authentication_error(): + """Test para la excepción AuthenticationError""" + error = AuthenticationError("Unauthorized access", 401) + assert str(error) == "Unauthorized access" + assert error.status_code == 401 + assert isinstance(error, TenableAPIError) + + +def test_unexpected_api_response(): + """Test para la excepción UnexpectedAPIResponse""" + error = UnexpectedAPIResponse("Unexpected error", 502) + assert str(error) == "Unexpected error" + assert error.status_code == 502 + assert isinstance(error, TenableAPIError) + + +def test_bad_request_error(): + """Test para la excepción BadRequestError""" + error = BadRequestError("Bad request", 400) + assert str(error) == "Bad request" + assert error.status_code == 400 + assert isinstance(error, TenableAPIError) + + +# Ejecución de las pruebas +if __name__ == "__main__": + pytest.main() diff --git a/tests/unit/plugins/module_utils/test_parameter_actions.py b/tests/unit/plugins/module_utils/test_parameter_actions.py new file mode 100644 index 0000000..5ab70cb --- /dev/null +++ b/tests/unit/plugins/module_utils/test_parameter_actions.py @@ -0,0 +1,131 @@ +# Copyright: (c) 2024, Fernando Mendieta +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import MagicMock + +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import add_custom_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_complex_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_query_parameters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import build_tag_values_payload +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_multiple_filters +from ansible_collections.valkiriaaquatica.tenable.plugins.module_utils.parameter_actions import handle_special_filter + + +def test_build_query_parameters(): + """Test building query parameters.""" + params = build_query_parameters(a="value1", b=["list1", "list2"], c=True, d=None) + assert params == {"a": "value1", "b": "list1,list2", "c": "true"} + + +def test_handle_multiple_filters(): + """Test handling asset vulnerability filters.""" + query_params = {} + filters = [ + {"type": "type1", "operator": "eq", "value": "value1"}, + {"type": "type2", "operator": "neq", "value": "value2"}, + ] + result = handle_multiple_filters(query_params, filters) + assert result == { + "filter.0.filter": "type1", + "filter.0.quality": "eq", + "filter.0.value": "value1", + "filter.1.filter": "type2", + "filter.1.quality": "neq", + "filter.1.value": "value2", + } + + +def test_handle_special_filter(): + """Test handling special filter.""" + query_params = {} + filters = [ + {"type": "type1", "operator": "eq", "value": "value1"}, + {"type": "type2", "operator": "neq", "value": "value2"}, + ] + result = handle_special_filter(query_params, filters) + assert result == {"f": "type1:eq:value1&type2:neq:value2"} + + +def test_add_custom_filters(): + """Test adding custom filters.""" + query_params = {} + filters = [ + {"type": "type1", "operator": "eq", "value": "value1"}, + {"type": "type2", "operator": "neq", "value": "value2"}, + ] + result = add_custom_filters(query_params, filters, handle_multiple_filters) + assert result == { + "filter.0.filter": "type1", + "filter.0.quality": "eq", + "filter.0.value": "value1", + "filter.1.filter": "type2", + "filter.1.quality": "neq", + "filter.1.value": "value2", + } + + +def test_build_payload(): + """Test building payload from module params.""" + module = MagicMock() + module.params = { + "param1": "value1", + "param2": {"subparam1": "subvalue1", "subparam2": None}, + "param3": None, + "param4": ["item1", "item2"], + } + keys = ["param1", "param2", "param3", "param4"] + payload = build_payload(module, keys) + assert payload == {"param1": "value1", "param2": {"subparam1": "subvalue1"}, "param4": ["item1", "item2"]} + + +def test_build_complex_payload(): + """Test building complex payload.""" + params = { + "uuid": "test_uuid", + "settings": { + "setting1": "value1", + "setting2": {"subsetting1": "subvalue1", "subsetting2": None}, + "setting3": None, + }, + "credentials": {"cred1": {"user": "user1", "password": None}, "cred2": {"api_key": "key2"}}, + "plugin_configurations": [ + {"plugin_family_name": "family1", "plugins": [{"plugin_id": "plugin1", "status": "enabled"}]} + ], + } + payload = build_complex_payload(params) + assert payload == { + "uuid": "test_uuid", + "settings": {"setting1": "value1", "setting2": {"subsetting1": "subvalue1"}}, + "credentials": {"cred1": {"user": "user1"}, "cred2": {"api_key": "key2"}}, + "plugins": {"family1": {"individual": {"plugin1": "enabled"}}}, + } + + +def test_build_tag_values_payload(): + """Test building tag values payload.""" + params = { + "category_name": "category1", + "category_uuid": "uuid1", + "value": "value1", + "category_description": "desc1", + "description": None, + "access_control": {"control1": "value1", "control2": None}, + "filters": {"filter1": "value1", "filter2": None}, + } + payload = build_tag_values_payload(params) + assert payload == { + "category_name": "category1", + "category_uuid": "uuid1", + "value": "value1", + "category_description": "desc1", + "access_control": {"control1": "value1"}, + "filters": {"filter1": "value1"}, + } diff --git a/tests/unit/plugins/modules/test_add_agent_to_group.py b/tests/unit/plugins/modules/test_add_agent_to_group.py new file mode 100644 index 0000000..2e10997 --- /dev/null +++ b/tests/unit/plugins/modules/test_add_agent_to_group.py @@ -0,0 +1,33 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.add_agent_to_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "add_agent_to_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_add_agent_to_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": "123456", + "agent_id": "654321", + } + + with patch(BASE_MODULE_PATH + "add_agent_to_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once() diff --git a/tests/unit/plugins/modules/test_add_agents_to_a_group.py b/tests/unit/plugins/modules/test_add_agents_to_a_group.py new file mode 100644 index 0000000..9fde377 --- /dev/null +++ b/tests/unit/plugins/modules/test_add_agents_to_a_group.py @@ -0,0 +1,68 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.add_agents_to_a_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "add_agents_to_a_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_add_agents_to_a_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": "123456789", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["1111"], + "not_items": ["98765"], + } + with patch(BASE_MODULE_PATH + "add_agents_to_a_group.build_payload") as mock_build_payload: + mock_build_payload.return_value = { + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + } + with patch(BASE_MODULE_PATH + "add_agents_to_a_group.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["criteria", "items", "not_items"]) + mock_run_module.assert_called_once_with( + mock_module.return_value, + "scanners/null/agent-groups/123456789/agents/_bulk/add", + method="POST", + data={ + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + }, + ) diff --git a/tests/unit/plugins/modules/test_add_agents_to_a_network.py b/tests/unit/plugins/modules/test_add_agents_to_a_network.py new file mode 100644 index 0000000..5ac9ece --- /dev/null +++ b/tests/unit/plugins/modules/test_add_agents_to_a_network.py @@ -0,0 +1,74 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.add_agents_to_a_network import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "add_agents_to_a_network.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_add_agents_to_a_network(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "network_uuid": "123456789", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + } + + with patch(BASE_MODULE_PATH + "add_agents_to_a_network.build_payload") as mock_build_payload: + mock_build_payload.return_value = { + "network_uuid": "123456789", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + } + + with patch(BASE_MODULE_PATH + "add_agents_to_a_network.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["network_uuid", "criteria", "items", "not_items"] + ) + mock_run_module.assert_called_once_with( + mock_module.return_value, + "scanners/null/agents/_bulk/addToNetwork", + method="POST", + data={ + "network_uuid": "123456789", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + }, + ) diff --git a/tests/unit/plugins/modules/test_add_or_remove_asset_tags.py b/tests/unit/plugins/modules/test_add_or_remove_asset_tags.py new file mode 100644 index 0000000..485030f --- /dev/null +++ b/tests/unit/plugins/modules/test_add_or_remove_asset_tags.py @@ -0,0 +1,47 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.add_or_remove_asset_tags import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "add_or_remove_asset_tags.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_add_or_remove_asset_tags(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "action": "add", + "assets": ["123456789"], + "tags": ["first_tag_uuid", "second_tag_uuid"], + } + + with patch(BASE_MODULE_PATH + "add_or_remove_asset_tags.build_payload") as mock_build_payload: + mock_build_payload.return_value = { + "action": "add", + "assets": ["123456789"], + "tags": ["first_tag_uuid", "second_tag_uuid"], + } + + with patch(BASE_MODULE_PATH + "add_or_remove_asset_tags.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["action", "assets", "tags"]) + mock_run_module.assert_called_once_with( + mock_module.return_value, + "tags/assets/assignments", + method="POST", + data={"action": "add", "assets": ["123456789"], "tags": ["first_tag_uuid", "second_tag_uuid"]}, + ) diff --git a/tests/unit/plugins/modules/test_add_scanner_to_scanner_group.py b/tests/unit/plugins/modules/test_add_scanner_to_scanner_group.py new file mode 100644 index 0000000..069acae --- /dev/null +++ b/tests/unit/plugins/modules/test_add_scanner_to_scanner_group.py @@ -0,0 +1,35 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.add_scanner_to_scanner_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "add_scanner_to_scanner_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_add_scanner_to_scanner_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 12345, + "scanner_id": 67890, + } + + endpoint = "scanner-groups/12345/scanners/67890" + + with patch(BASE_MODULE_PATH + "add_scanner_to_scanner_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST") diff --git a/tests/unit/plugins/modules/test_add_user_to_group.py b/tests/unit/plugins/modules/test_add_user_to_group.py new file mode 100644 index 0000000..b35dca1 --- /dev/null +++ b/tests/unit/plugins/modules/test_add_user_to_group.py @@ -0,0 +1,35 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.add_user_to_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "add_user_to_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_add_user_to_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 1234, + "user_id": 5444, + } + + endpoint = "groups/1234/users/5444" + + with patch(BASE_MODULE_PATH + "add_user_to_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST") diff --git a/tests/unit/plugins/modules/test_agent_profile_operations.py b/tests/unit/plugins/modules/test_agent_profile_operations.py new file mode 100644 index 0000000..cd96a16 --- /dev/null +++ b/tests/unit/plugins/modules/test_agent_profile_operations.py @@ -0,0 +1,104 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.agent_profile_operations import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "agent_profile_operations.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_agent_profile_operations_assign(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "action": "assign", + "profile_uuid": "1233", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["34334"], + "not_items": ["98765"], + } + + payload = { + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["34334"], + "not_items": ["98765"], + "profile_uuid": "1233", + } + + endpoint = "scanners/null/agents/_bulk/assignToProfile" + + with patch(BASE_MODULE_PATH + "agent_profile_operations.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "agent_profile_operations.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["criteria", "items", "not_items", "profile_uuid"] + ) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) + + +def test_agent_profile_operations_remove(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "action": "remove", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["34334"], + "not_items": ["98765"], + } + + payload = { + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["34334"], + "not_items": ["98765"], + } + + endpoint = "scanners/null/agents/_bulk/assignToProfile" + + with patch(BASE_MODULE_PATH + "agent_profile_operations.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "agent_profile_operations.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["criteria", "items", "not_items", "profile_uuid"] + ) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_allow_control_of_running_scans.py b/tests/unit/plugins/modules/test_allow_control_of_running_scans.py new file mode 100644 index 0000000..9424a8f --- /dev/null +++ b/tests/unit/plugins/modules/test_allow_control_of_running_scans.py @@ -0,0 +1,42 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.allow_control_of_running_scans import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "allow_control_of_running_scans.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_allow_control_of_running_scans(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_id": 123456, + "scan_id": 987, + "action": "resume", + } + + payload = {"action": "resume"} + + endpoint = "scanners/123456/scans/987" + + with patch(BASE_MODULE_PATH + "allow_control_of_running_scans.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "allow_control_of_running_scans.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["action"]) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_assign_attributes_to_asset.py b/tests/unit/plugins/modules/test_assign_attributes_to_asset.py new file mode 100644 index 0000000..1814fd8 --- /dev/null +++ b/tests/unit/plugins/modules/test_assign_attributes_to_asset.py @@ -0,0 +1,38 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.assign_attributes_to_asset import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "assign_attributes_to_asset.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_assign_attributes_to_asset(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_uuid": "12345", + "attributes": [{"id": "123456", "value": "Dallas"}, {"id": "23897", "value": "Paris"}], + } + + payload = {"attributes": [{"id": "123456", "value": "Dallas"}, {"id": "23897", "value": "Paris"}]} + + endpoint = "api/v3/assets/12345/attributes" + + with patch(BASE_MODULE_PATH + "assign_attributes_to_asset.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "assign_attributes_to_asset.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["attributes"]) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="PUT", data=payload) diff --git a/tests/unit/plugins/modules/test_assign_scanners.py b/tests/unit/plugins/modules/test_assign_scanners.py new file mode 100644 index 0000000..085fde1 --- /dev/null +++ b/tests/unit/plugins/modules/test_assign_scanners.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.assign_scanners import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "assign_scanners.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_assign_scanners(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "network_id": "12345", + "scanner_uuid": "9874", + } + + endpoint = "networks/12345/scanners/9874" + + with patch(BASE_MODULE_PATH + "assign_scanners.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST") diff --git a/tests/unit/plugins/modules/test_assign_single_attribute_to_asset.py b/tests/unit/plugins/modules/test_assign_single_attribute_to_asset.py new file mode 100644 index 0000000..ed5b2bc --- /dev/null +++ b/tests/unit/plugins/modules/test_assign_single_attribute_to_asset.py @@ -0,0 +1,39 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.assign_single_attribute_to_asset import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "assign_single_attribute_to_asset.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_assign_single_attribute_to_asset(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_uuid": "123456", + "attribute_id": "987465", + "value": "this_is_the_new_value", + } + + payload = {"value": "this_is_the_new_value"} + + endpoint = "api/v3/assets/123456/attributes/987465" + + with patch(BASE_MODULE_PATH + "assign_single_attribute_to_asset.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "assign_single_attribute_to_asset.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["value"]) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="PUT", data=payload) diff --git a/tests/unit/plugins/modules/test_check_agent_group_operation_status.py b/tests/unit/plugins/modules/test_check_agent_group_operation_status.py new file mode 100644 index 0000000..bcbbc4c --- /dev/null +++ b/tests/unit/plugins/modules/test_check_agent_group_operation_status.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.check_agent_group_operation_status import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "check_agent_group_operation_status.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_check_agent_group_operation_status(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": "1233", + "task_uuid": "321", + } + + endpoint = "scanners/null/agent-groups/1233/agents/_bulk/321" + + with patch(BASE_MODULE_PATH + "check_agent_group_operation_status.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_check_agent_operation_status.py b/tests/unit/plugins/modules/test_check_agent_operation_status.py new file mode 100644 index 0000000..c915fcb --- /dev/null +++ b/tests/unit/plugins/modules/test_check_agent_operation_status.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.check_agent_operation_status import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "check_agent_operation_status.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_check_agent_operation_status(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "task_uuid": "321", + } + + endpoint = "scanners/null/agents/_bulk/321" + + with patch(BASE_MODULE_PATH + "check_agent_operation_status.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_check_scan_export_status.py b/tests/unit/plugins/modules/test_check_scan_export_status.py new file mode 100644 index 0000000..a0a60bc --- /dev/null +++ b/tests/unit/plugins/modules/test_check_scan_export_status.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.check_scan_export_status import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "check_scan_export_status.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_check_scan_export_status(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "1234", + "file_id": "24332r34-csv", + } + + endpoint = "scans/1234/export/24332r34-csv/status" + + with patch(BASE_MODULE_PATH + "check_scan_export_status.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_copy_scan.py b/tests/unit/plugins/modules/test_copy_scan.py new file mode 100644 index 0000000..d63a03d --- /dev/null +++ b/tests/unit/plugins/modules/test_copy_scan.py @@ -0,0 +1,39 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.copy_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "copy_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_copy_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "12345", + "folder_id": "67890", + "name": "new scan", + } + + payload = {"folder_id": "67890", "name": "new scan"} + + endpoint = "scans/12345/copy" + + with patch(BASE_MODULE_PATH + "copy_scan.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "copy_scan.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["folder_id", "name"]) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_create_agent_exclusion.py b/tests/unit/plugins/modules/test_create_agent_exclusion.py new file mode 100644 index 0000000..d50265b --- /dev/null +++ b/tests/unit/plugins/modules/test_create_agent_exclusion.py @@ -0,0 +1,55 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_agent_exclusion import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "create_agent_exclusion.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_create_agent_exclusion(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "name", + "description": "description", + "schedule": { + "enabled": True, + "starttime": "2023-01-01 00:00:00", + "endtime": "2023-12-31 23:59:59", + "timezone": "US/Pacific", + "rrules": {"freq": "ONETIME", "interval": 1, "byweekday": "SU", "bymonthday": 1}, + }, + } + + payload = { + "name": "name", + "description": "description", + "schedule": { + "enabled": True, + "starttime": "2023-01-01 00:00:00", + "endtime": "2023-12-31 23:59:59", + "timezone": "US/Pacific", + "rrules": {"freq": "ONETIME", "interval": 1, "byweekday": "SU", "bymonthday": 1}, + }, + } + + endpoint = "scanners/null/agents/exclusions" + + with patch(BASE_MODULE_PATH + "create_agent_exclusion.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "create_agent_exclusion.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name", "description", "schedule"]) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_create_agent_group.py b/tests/unit/plugins/modules/test_create_agent_group.py new file mode 100644 index 0000000..545a8dc --- /dev/null +++ b/tests/unit/plugins/modules/test_create_agent_group.py @@ -0,0 +1,43 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_agent_group import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_agent_group.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_create_agent_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "test_group", + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_agent_group.build_payload" + ) as mock_build_payload: + mock_build_payload.return_value = {"name": "test_group"} + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_agent_group.run_module" + ) as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) + mock_run_module.assert_called_once_with( + mock_module.return_value, "scanners/null/agent-groups", method="POST", data={"name": "test_group"} + ) diff --git a/tests/unit/plugins/modules/test_create_attribute.py b/tests/unit/plugins/modules/test_create_attribute.py new file mode 100644 index 0000000..d60357a --- /dev/null +++ b/tests/unit/plugins/modules/test_create_attribute.py @@ -0,0 +1,45 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_attribute import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "create_attribute.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_create_attribute(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "attributes": [ + {"name": "Location", "description": "The geographical location of the asset"}, + {"name": "Department", "description": "The department to which the asset belongs"}, + ], + } + + payload = { + "attributes": [ + {"name": "Location", "description": "The geographical location of the asset"}, + {"name": "Department", "description": "The department to which the asset belongs"}, + ] + } + + endpoint = "api/v3/assets/attributes" + + with patch(BASE_MODULE_PATH + "create_attribute.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "create_attribute.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["attributes"]) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_create_exclusion.py b/tests/unit/plugins/modules/test_create_exclusion.py new file mode 100644 index 0000000..0e26e56 --- /dev/null +++ b/tests/unit/plugins/modules/test_create_exclusion.py @@ -0,0 +1,81 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_exclusion import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_exclusion.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_create_exclusion(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "test_exclusion", + "members": "192.168.1.10,192.168.1.11", + "description": "i am the exclusion", + "schedule": { + "enabled": True, + "starttime": "2023-04-01 09:00:00", + "endtime": "2023-04-01 17:00:00", + "timezone": "America/New_York", + "rrules": {"freq": "WEEKLY", "interval": 1, "byweekday": "MO,TU,WE,TH,FR"}, + }, + "network_id": "123456", + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_exclusion.build_payload" + ) as mock_build_payload: + mock_build_payload.return_value = { + "name": "test_exclusion", + "members": "192.168.1.10,192.168.1.11", + "description": "i am the exclusion", + "schedule": { + "enabled": True, + "starttime": "2023-04-01 09:00:00", + "endtime": "2023-04-01 17:00:00", + "timezone": "America/New_York", + "rrules": {"freq": "WEEKLY", "interval": 1, "byweekday": "MO,TU,WE,TH,FR"}, + }, + "network_id": "123456", + } + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_exclusion.run_module" + ) as mock_run_module: + main() + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["name", "members", "description", "schedule", "network_id"] + ) + mock_run_module.assert_called_once_with( + mock_module.return_value, + "exclusions", + method="POST", + data={ + "name": "test_exclusion", + "members": "192.168.1.10,192.168.1.11", + "description": "i am the exclusion", + "schedule": { + "enabled": True, + "starttime": "2023-04-01 09:00:00", + "endtime": "2023-04-01 17:00:00", + "timezone": "America/New_York", + "rrules": {"freq": "WEEKLY", "interval": 1, "byweekday": "MO,TU,WE,TH,FR"}, + }, + "network_id": "123456", + }, + ) diff --git a/tests/unit/plugins/modules/test_create_folder.py b/tests/unit/plugins/modules/test_create_folder.py new file mode 100644 index 0000000..81c6f9c --- /dev/null +++ b/tests/unit/plugins/modules/test_create_folder.py @@ -0,0 +1,42 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_folder import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_folder.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_create_folder(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "test_folder", + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_folder.build_payload" + ) as mock_build_payload: + mock_build_payload.return_value = {"name": "test_folder"} + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_folder.run_module" + ) as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) + mock_run_module.assert_called_once_with( + mock_module.return_value, "folders", method="POST", data={"name": "test_folder"} + ) diff --git a/tests/unit/plugins/modules/test_create_group.py b/tests/unit/plugins/modules/test_create_group.py new file mode 100644 index 0000000..adf29b0 --- /dev/null +++ b/tests/unit/plugins/modules/test_create_group.py @@ -0,0 +1,37 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "create_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_create_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "Example Group", + } + + payload = {"name": "Example Group"} + + endpoint = "groups" + + with patch(BASE_MODULE_PATH + "create_group.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "create_group.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_create_managed_credential.py b/tests/unit/plugins/modules/test_create_managed_credential.py new file mode 100644 index 0000000..74e630b --- /dev/null +++ b/tests/unit/plugins/modules/test_create_managed_credential.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_managed_credential import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "create_managed_credential.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_create_managed_credential(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "name", + "description": "description", + "type": "type", + "settings": {"domain": "domain", "username": "username", "auth_method": "Password", "password": "password"}, + "permissions": [{"grantee_uuid": "grantee_uuid", "type": "user", "permissions": 64, "name": "name"}], + } + + payload = { + "name": "name", + "description": "description", + "type": "type", + "settings": {"domain": "domain", "username": "username", "auth_method": "Password", "password": "password"}, + "permissions": [{"grantee_uuid": "grantee_uuid", "type": "user", "permissions": 64, "name": "name"}], + } + + endpoint = "credentials" + + with patch(BASE_MODULE_PATH + "create_managed_credential.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "create_managed_credential.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["name", "description", "type", "settings", "permissions"] + ) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_create_network.py b/tests/unit/plugins/modules/test_create_network.py new file mode 100644 index 0000000..246f7b6 --- /dev/null +++ b/tests/unit/plugins/modules/test_create_network.py @@ -0,0 +1,54 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_network import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_network.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_create_network(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "test_network", + "description": "this is the description", + "assets_ttl_days": 50, + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_network.build_payload" + ) as mock_build_payload: + mock_build_payload.return_value = { + "name": "test_network", + "description": "this is the description", + "assets_ttl_days": 50, + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_network.run_module" + ) as mock_run_module: + main() + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["name", "description", "assets_ttl_days"] + ) + mock_run_module.assert_called_once_with( + mock_module.return_value, + "networks", + method="POST", + data={"name": "test_network", "description": "this is the description", "assets_ttl_days": 50}, + ) diff --git a/tests/unit/plugins/modules/test_create_remediation_scan.py b/tests/unit/plugins/modules/test_create_remediation_scan.py new file mode 100644 index 0000000..a846653 --- /dev/null +++ b/tests/unit/plugins/modules/test_create_remediation_scan.py @@ -0,0 +1,91 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_remediation_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "create_remediation_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_create_remediation_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "uuid": "12345", + "settings": { + "name": "remediation scan", + "description": "Scan to remediate issues", + "scanner_id": "scanner_id", + "target_network_uuid": "target_network_uuid", + "scan_time_window": 180, + "text_targets": "192.0.2.1/24", + "file_targets": "targets.txt", + "tag_targets": ["tag1", "tag2"], + "agent_group_id": ["agent_group_1", "agent_group_2"], + "emails": "user@example.com", + "acls": [ + {"permissions": 64, "owner": 1, "display_name": "Admin", "name": "admin", "id": 1, "type": "group"} + ], + }, + "credentials": { + "add": { + "Host": { + "Windows": [ + {"domain": "domain", "username": "username", "auth_method": "Password", "password": "password"} + ] + } + } + }, + "enabled_plugins": [12345, 654212], + } + + payload = { + "uuid": "12345", + "settings": { + "name": "remediation scan", + "description": "Scan to remediate issues", + "scanner_id": "scanner_id", + "target_network_uuid": "target_network_uuid", + "scan_time_window": 180, + "text_targets": "192.0.2.1/24", + "file_targets": "targets.txt", + "tag_targets": ["tag1", "tag2"], + "agent_group_id": ["agent_group_1", "agent_group_2"], + "emails": "user@example.com", + "acls": [ + {"permissions": 64, "owner": 1, "display_name": "Admin", "name": "admin", "id": 1, "type": "group"} + ], + }, + "credentials": { + "add": { + "Host": { + "Windows": [ + {"domain": "domain", "username": "username", "auth_method": "Password", "password": "password"} + ] + } + } + }, + "enabled_plugins": [12345, 654212], + } + + endpoint = "scans/remediation" + + with patch(BASE_MODULE_PATH + "create_remediation_scan.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "create_remediation_scan.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["uuid", "settings", "credentials", "enabled_plugins"] + ) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_create_report.py b/tests/unit/plugins/modules/test_create_report.py new file mode 100644 index 0000000..683a920 --- /dev/null +++ b/tests/unit/plugins/modules/test_create_report.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_report import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "create_report.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_create_report(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "report_test", + "template_name": "host_vulns_summary", + "filters": [ + {"property": "plugin_id", "operator": "eq", "value": [12345]}, + {"property": "source", "operator": "eq", "value": ["AWS"]}, + ], + } + + payload = { + "name": "report_test", + "template_name": "host_vulns_summary", + "filters": [ + {"property": "plugin_id", "operator": "eq", "value": [12345]}, + {"property": "source", "operator": "eq", "value": ["AWS"]}, + ], + } + + endpoint = "reports/export" + + with patch(BASE_MODULE_PATH + "create_report.build_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "create_report.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name", "template_name", "filters"]) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_create_scan.py b/tests/unit/plugins/modules/test_create_scan.py new file mode 100644 index 0000000..91c7860 --- /dev/null +++ b/tests/unit/plugins/modules/test_create_scan.py @@ -0,0 +1,87 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "create_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_create_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "uuid": "template_scan_uuid", + "settings": { + "name": "name_scan_creation", + "folder_id": 111, + "scanner_id": 123456, + "launch": "ON_DEMAND", + "rrules": "WEEKLY", + "timezone": "Atlantic/Madeira", + "text_targets": "192.168.1.1,192.168.1.2", + }, + "credentials": { + "add": { + "Host": { + "Windows": [ + {"domain": "domain", "username": "username", "auth_method": "Password", "password": "password"} + ] + } + } + }, + "plugin_configurations": [ + { + "plugin_family_name": "Red Hat Local Security Checks", + "plugins": [{"plugin_id": "79798", "status": "enabled"}, {"plugin_id": "79799", "status": "disabled"}], + } + ], + } + + payload = { + "uuid": "template_scan_uuid", + "settings": { + "name": "name_scan_creation", + "folder_id": 111, + "scanner_id": 123456, + "launch": "ON_DEMAND", + "rrules": "WEEKLY", + "timezone": "Atlantic/Madeira", + "text_targets": "192.168.1.1,192.168.1.2", + }, + "credentials": { + "add": { + "Host": { + "Windows": [ + {"domain": "domain", "username": "username", "auth_method": "Password", "password": "password"} + ] + } + } + }, + "plugin_configurations": [ + { + "plugin_family_name": "Red Hat Local Security Checks", + "plugins": [{"plugin_id": "79798", "status": "enabled"}, {"plugin_id": "79799", "status": "disabled"}], + } + ], + } + + endpoint = "scans" + + with patch(BASE_MODULE_PATH + "create_scan.build_complex_payload") as mock_build_payload: + mock_build_payload.return_value = payload + + with patch(BASE_MODULE_PATH + "create_scan.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value.params) + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_create_tag_category.py b/tests/unit/plugins/modules/test_create_tag_category.py new file mode 100644 index 0000000..7d6b2a2 --- /dev/null +++ b/tests/unit/plugins/modules/test_create_tag_category.py @@ -0,0 +1,47 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_tag_category import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_tag_category.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_create_tag_category(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "test_tag_category", + "description": "this is the description", + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_tag_category.build_payload" + ) as mock_build_payload: + mock_build_payload.return_value = {"name": "test_tag_category", "description": "this is the description"} + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_tag_category.run_module" + ) as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name", "description"]) + mock_run_module.assert_called_once_with( + mock_module.return_value, + "tags/categories", + method="POST", + data={"name": "test_tag_category", "description": "this is the description"}, + ) diff --git a/tests/unit/plugins/modules/test_create_tag_value.py b/tests/unit/plugins/modules/test_create_tag_value.py new file mode 100644 index 0000000..089989a --- /dev/null +++ b/tests/unit/plugins/modules/test_create_tag_value.py @@ -0,0 +1,47 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_tag_value import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_tag_value.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_create_tag_value(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "category_name": "test_category", + "value": "test_value", + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_tag_value.build_tag_values_payload" + ) as mock_build_payload: + mock_build_payload.return_value = {"category_name": "test_category", "value": "test_value"} + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.create_tag_value.run_module" + ) as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value.params) + mock_run_module.assert_called_once_with( + mock_module.return_value, + "tags/values", + method="POST", + data={"category_name": "test_category", "value": "test_value"}, + ) diff --git a/tests/unit/plugins/modules/test_delete_agent_exclusion.py b/tests/unit/plugins/modules/test_delete_agent_exclusion.py new file mode 100644 index 0000000..bf6a52d --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_agent_exclusion.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_agent_exclusion import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_agent_exclusion.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_agent_exclusion(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "exclusion_id": 12345, + } + + endpoint = "scanners/null/agents/exclusions/12345" + + with patch(BASE_MODULE_PATH + "delete_agent_exclusion.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_agent_group.py b/tests/unit/plugins/modules/test_delete_agent_group.py new file mode 100644 index 0000000..968c3c2 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_agent_group.py @@ -0,0 +1,36 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_agent_group import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_agent_group.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_delete_agent_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": "123456", + } + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_agent_group.run_module" + ) as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "scanners/null/agent-groups/123456", method="DELETE" + ) diff --git a/tests/unit/plugins/modules/test_delete_an_exclusion.py b/tests/unit/plugins/modules/test_delete_an_exclusion.py new file mode 100644 index 0000000..ffb54c4 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_an_exclusion.py @@ -0,0 +1,35 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_an_exclusion import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_an_exclusion.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_delete_an_exclusion(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "exclusion_id": 12345, + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_an_exclusion.run_module" + ) as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "exclusions/12345", method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_asset.py b/tests/unit/plugins/modules/test_delete_asset.py new file mode 100644 index 0000000..416e415 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_asset.py @@ -0,0 +1,35 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_asset import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_asset.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_delete_asset(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_uuid": "11111", + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_asset.run_module" + ) as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "workbenches/assets/11111", method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_attribute.py b/tests/unit/plugins/modules/test_delete_attribute.py new file mode 100644 index 0000000..4a78b48 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_attribute.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_attribute import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_attribute.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_attribute(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "attribute_id": "123", + } + + endpoint = "api/v3/assets/attributes/123" + + with patch(BASE_MODULE_PATH + "delete_attribute.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_attribute_from_asset.py b/tests/unit/plugins/modules/test_delete_attribute_from_asset.py new file mode 100644 index 0000000..0a1cf35 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_attribute_from_asset.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_attribute_from_asset import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_attribute_from_asset.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_attribute_from_asset(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_uuid": "123456", + "attribute_id": "987465", + } + + endpoint = "api/v3/assets/123456/attributes/987465" + + with patch(BASE_MODULE_PATH + "delete_attribute_from_asset.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_attributes_from_asset.py b/tests/unit/plugins/modules/test_delete_attributes_from_asset.py new file mode 100644 index 0000000..6fb480d --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_attributes_from_asset.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_attributes_from_asset import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_attributes_from_asset.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_attributes_from_asset(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_uuid": "12345", + } + + endpoint = "api/v3/assets/12345/attributes" + + with patch(BASE_MODULE_PATH + "delete_attributes_from_asset.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_folder.py b/tests/unit/plugins/modules/test_delete_folder.py new file mode 100644 index 0000000..478f4d6 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_folder.py @@ -0,0 +1,35 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_folder import main + + +@pytest.fixture +def mock_module(): + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_folder.AnsibleModule", autospec=True + ) as mock: + yield mock + + +def test_delete_folder(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "folder_id": 12346, + } + + with patch( + "ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_folder.run_module" + ) as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "folders/12346", method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_group.py b/tests/unit/plugins/modules/test_delete_group.py new file mode 100644 index 0000000..c2eff0c --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_group.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 1233, + } + + endpoint = "groups/1233" + + with patch(BASE_MODULE_PATH + "delete_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_managed_credential.py b/tests/unit/plugins/modules/test_delete_managed_credential.py new file mode 100644 index 0000000..3506b85 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_managed_credential.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_managed_credential import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_managed_credential.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_managed_credential(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "credential_uuid": "12346", + } + + endpoint = "credentials/12346" + + with patch(BASE_MODULE_PATH + "delete_managed_credential.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_network.py b/tests/unit/plugins/modules/test_delete_network.py new file mode 100644 index 0000000..a6c5332 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_network.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_network import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_network.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_network(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "network_id": "12345", + } + + with patch(BASE_MODULE_PATH + "delete_network.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "networks/12345", method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_scan.py b/tests/unit/plugins/modules/test_delete_scan.py new file mode 100644 index 0000000..cfa19e0 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_scan.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "12345", + } + + with patch(BASE_MODULE_PATH + "delete_scan.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scans/12345", method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_scan_history.py b/tests/unit/plugins/modules/test_delete_scan_history.py new file mode 100644 index 0000000..387bdb9 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_scan_history.py @@ -0,0 +1,49 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_scan_history import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_scan_history.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_scan_history(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + "history_id": "23545", + "exclude_rollover": True, + } + + endpoint = "scans/123456/history/23545?delete_rollovers=True" + + with patch(BASE_MODULE_PATH + "delete_scan_history.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") + + +def test_delete_scan_history_without_exclude_rollover(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + "history_id": "23545", + "exclude_rollover": False, + } + + endpoint = "scans/123456/history/23545" + + with patch(BASE_MODULE_PATH + "delete_scan_history.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_scanner.py b/tests/unit/plugins/modules/test_delete_scanner.py new file mode 100644 index 0000000..35bebc5 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_scanner.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_scanner import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_scanner.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_scanner(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_id": 12345, + } + + endpoint = "scanners/12345" + + with patch(BASE_MODULE_PATH + "delete_scanner.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_scanner_group.py b/tests/unit/plugins/modules/test_delete_scanner_group.py new file mode 100644 index 0000000..a417d64 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_scanner_group.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_scanner_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_scanner_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_scanner_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 12345, + } + + endpoint = "scanner-groups/12345" + + with patch(BASE_MODULE_PATH + "delete_scanner_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_tag_category.py b/tests/unit/plugins/modules/test_delete_tag_category.py new file mode 100644 index 0000000..f65f32d --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_tag_category.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_tag_category import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_tag_category.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_tag_category(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "category_uuid": "fake_uuid", + } + + with patch(BASE_MODULE_PATH + "delete_tag_category.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "tags/categories/fake_uuid", method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_tag_value.py b/tests/unit/plugins/modules/test_delete_tag_value.py new file mode 100644 index 0000000..62231b5 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_tag_value.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_tag_value import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_tag_value.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_tag_value(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "value_uuid": "123456", + } + + endpoint = "tags/values/123456" + + with patch(BASE_MODULE_PATH + "delete_tag_value.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_delete_user_from_group.py b/tests/unit/plugins/modules/test_delete_user_from_group.py new file mode 100644 index 0000000..9075ab9 --- /dev/null +++ b/tests/unit/plugins/modules/test_delete_user_from_group.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.delete_user_from_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "delete_user_from_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_delete_user_from_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 123, + "user_id": 433, + } + + endpoint = "groups/123/users/433" + + with patch(BASE_MODULE_PATH + "delete_user_from_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="DELETE") diff --git a/tests/unit/plugins/modules/test_download_exported_scan.py b/tests/unit/plugins/modules/test_download_exported_scan.py new file mode 100644 index 0000000..15ad125 --- /dev/null +++ b/tests/unit/plugins/modules/test_download_exported_scan.py @@ -0,0 +1,42 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import mock_open +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.download_exported_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "download_exported_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_download_exported_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "1234", + "file_id": "wedcdfgdsfr-csv", + "download_path": "/tmp/descarga.csv", + } + + endpoint = "scans/1234/export/wedcdfgdsfr-csv/download" + + with patch(BASE_MODULE_PATH + "download_exported_scan.TenableAPI") as mock_tenable_api, patch( + "builtins.open", mock_open() + ) as mock_file: + instance = mock_tenable_api.return_value + instance.request.return_value = {"data": b"fake_data"} + + main() + + instance.request.assert_called_once_with("GET", endpoint) + mock_file.assert_called_once_with("/tmp/descarga.csv", "wb") + mock_file().write.assert_called_once_with(b"fake_data") diff --git a/tests/unit/plugins/modules/test_download_report.py b/tests/unit/plugins/modules/test_download_report.py new file mode 100644 index 0000000..c069b91 --- /dev/null +++ b/tests/unit/plugins/modules/test_download_report.py @@ -0,0 +1,41 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import mock_open +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.download_report import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "download_report.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_download_report(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "report_uuid": "123456", + "download_path": "/tmp/report.pdf", + } + + endpoint = "reports/export/123456/download" + + with patch(BASE_MODULE_PATH + "download_report.TenableAPI") as mock_tenable_api, patch( + "builtins.open", mock_open() + ) as mock_file: + instance = mock_tenable_api.return_value + instance.request.return_value = {"data": b"fake_data"} + + main() + + instance.request.assert_called_once_with("GET", endpoint) + mock_file.assert_called_once_with("/tmp/report.pdf", "wb") + mock_file().write.assert_called_once_with(b"fake_data") diff --git a/tests/unit/plugins/modules/test_enable_schedule.py b/tests/unit/plugins/modules/test_enable_schedule.py new file mode 100644 index 0000000..a87b886 --- /dev/null +++ b/tests/unit/plugins/modules/test_enable_schedule.py @@ -0,0 +1,33 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.enable_schedule import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "enable_schedule.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_enable_schedule(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + "enabled": True, + } + + endpoint = "scans/123456/schedule" + payload = {"enabled": True} + + with patch(BASE_MODULE_PATH + "enable_schedule.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="PUT", data=payload) diff --git a/tests/unit/plugins/modules/test_for_stop_scan.py b/tests/unit/plugins/modules/test_for_stop_scan.py new file mode 100644 index 0000000..d8ce83f --- /dev/null +++ b/tests/unit/plugins/modules/test_for_stop_scan.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.force_stop_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "force_stop_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_force_stop_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + } + + with patch(BASE_MODULE_PATH + "force_stop_scan.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scans/123456/force-stop", method="POST") diff --git a/tests/unit/plugins/modules/test_force_stop_scan.py b/tests/unit/plugins/modules/test_force_stop_scan.py new file mode 100644 index 0000000..5e2a521 --- /dev/null +++ b/tests/unit/plugins/modules/test_force_stop_scan.py @@ -0,0 +1,29 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.force_stop_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "force_stop_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_force_stop_scan(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "scan_id": "123456", + } + + with patch(BASE_MODULE_PATH + "force_stop_scan.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scans/123456/force-stop", method="POST") diff --git a/tests/unit/plugins/modules/test_get_agent_configuration.py b/tests/unit/plugins/modules/test_get_agent_configuration.py new file mode 100644 index 0000000..ed4a37e --- /dev/null +++ b/tests/unit/plugins/modules/test_get_agent_configuration.py @@ -0,0 +1,29 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_agent_configuration import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_agent_configuration.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_agent_configuration(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "scanner_id": "123456", + } + + with patch(BASE_MODULE_PATH + "get_agent_configuration.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanners/123456/agents/config", method="GET") diff --git a/tests/unit/plugins/modules/test_get_agent_details.py b/tests/unit/plugins/modules/test_get_agent_details.py new file mode 100644 index 0000000..4428d53 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_agent_details.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_agent_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_agent_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_agent_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "agent_id": "11111", + } + + with patch(BASE_MODULE_PATH + "get_agent_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanners/null/agents", "11111", method="GET") diff --git a/tests/unit/plugins/modules/test_get_agent_exclusion_details.py b/tests/unit/plugins/modules/test_get_agent_exclusion_details.py new file mode 100644 index 0000000..500195c --- /dev/null +++ b/tests/unit/plugins/modules/test_get_agent_exclusion_details.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_agent_exclusion_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_agent_exclusion_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_agent_exclusion_details(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "exclusion_id": 124234, + } + + with patch(BASE_MODULE_PATH + "get_agent_exclusion_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "scanners/null/agents/exclusions/124234", method="GET" + ) diff --git a/tests/unit/plugins/modules/test_get_agent_group_details.py b/tests/unit/plugins/modules/test_get_agent_group_details.py new file mode 100644 index 0000000..dc51620 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_agent_group_details.py @@ -0,0 +1,77 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_agent_group_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_agent_group_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_agent_group_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": "123456", + "filters": [{"type": "platform", "operator": "eq", "value": "LINUX"}], + "filter_type": None, + "limit": None, + "offset": None, + "sort": None, + "wildcard_text": None, + "wildcard_fields": None, + } + + with patch(BASE_MODULE_PATH + "get_agent_group_details.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "get_agent_group_details.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "get_agent_group_details.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "get_agent_group_details.handle_special_filter" + ) as mock_handle_special_filter: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + filter_type=mock_module.return_value.params["filter_type"], + wildcard_text=mock_module.return_value.params["wildcard_text"], + wildcard_fields=mock_module.return_value.params["wildcard_fields"], + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ), + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agent-groups/123456" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_any_call( + filter_type=mock_module.return_value.params["filter_type"], + wildcard_text=mock_module.return_value.params["wildcard_text"], + wildcard_fields=mock_module.return_value.params["wildcard_fields"], + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ) diff --git a/tests/unit/plugins/modules/test_get_asset_activity_log.py b/tests/unit/plugins/modules/test_get_asset_activity_log.py new file mode 100644 index 0000000..d69a610 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_asset_activity_log.py @@ -0,0 +1,34 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_asset_activity_log import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_asset_activity_log.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_asset_activity_log(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_uuid": "123456", + } + + with patch(BASE_MODULE_PATH + "get_asset_activity_log.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "workbenches/assets/123456/activity", method="GET" + ) diff --git a/tests/unit/plugins/modules/test_get_asset_details.py b/tests/unit/plugins/modules/test_get_asset_details.py new file mode 100644 index 0000000..a176626 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_asset_details.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_asset_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_asset_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_asset_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_uuid": "11111", + } + + with patch(BASE_MODULE_PATH + "get_asset_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "assets", "11111", method="GET") diff --git a/tests/unit/plugins/modules/test_get_asset_information.py b/tests/unit/plugins/modules/test_get_asset_information.py new file mode 100644 index 0000000..f232d64 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_asset_information.py @@ -0,0 +1,50 @@ +# (c) 2024, Fernando Mendieta Ovejero (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_asset_information import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_asset_information.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_asset_information(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_id": "123456", + "all_fields": "full", + } + + with patch(BASE_MODULE_PATH + "get_asset_information.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "get_asset_information.build_query_parameters" + ) as mock_build_query_parameters: + + def query_params_func(): + return mock_build_query_parameters(all_fields=mock_module.return_value.params["all_fields"]) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "workbenches/assets/123456/info" + assert called_kwargs["method"] == "GET" + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_any_call( + all_fields=mock_module.return_value.params["all_fields"], + ) diff --git a/tests/unit/plugins/modules/test_get_asset_vulnerability_details.py b/tests/unit/plugins/modules/test_get_asset_vulnerability_details.py new file mode 100644 index 0000000..33bc5bc --- /dev/null +++ b/tests/unit/plugins/modules/test_get_asset_vulnerability_details.py @@ -0,0 +1,72 @@ +# (c) 2024, Fernando Mendieta Ovejero (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_asset_vulnerability_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_asset_vulnerability_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_asset_vulnerability_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_id": "123456", + "plugin_id": "987654", + "date_range": 5, + "filters": [{"type": "plugin.name", "operator": "match", "value": "RHEL"}], + "filter_search_type": None, + } + + with patch(BASE_MODULE_PATH + "get_asset_vulnerability_details.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "get_asset_vulnerability_details.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "get_asset_vulnerability_details.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "get_asset_vulnerability_details.handle_multiple_filters" + ) as mock_handle_multiple_filters: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ), + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "workbenches/assets/123456/vulnerabilities/987654/info" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + mock_build_query_parameters.assert_any_call( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ) + mock_handle_multiple_filters.assert_not_called() diff --git a/tests/unit/plugins/modules/test_get_category_details.py b/tests/unit/plugins/modules/test_get_category_details.py new file mode 100644 index 0000000..b57ce45 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_category_details.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_category_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_category_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_category_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "category_uuid": "12345", + } + + with patch(BASE_MODULE_PATH + "get_category_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "tags/categories/12345", method="GET") diff --git a/tests/unit/plugins/modules/test_get_exclusion_details.py b/tests/unit/plugins/modules/test_get_exclusion_details.py new file mode 100644 index 0000000..128eca1 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_exclusion_details.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_exclusion_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_exclusion_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_exclusion_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "exclusion_id": "11111", + } + + with patch(BASE_MODULE_PATH + "get_exclusion_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "exclusions", "11111", method="GET") diff --git a/tests/unit/plugins/modules/test_get_latest_scan_status.py b/tests/unit/plugins/modules/test_get_latest_scan_status.py new file mode 100644 index 0000000..da92be2 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_latest_scan_status.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_latest_scan_status import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_latest_scan_status.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_latest_scan_status(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456789", + } + + with patch(BASE_MODULE_PATH + "get_latest_scan_status.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scans/123456789/latest-status", method="GET") diff --git a/tests/unit/plugins/modules/test_get_managed_credential_details.py b/tests/unit/plugins/modules/test_get_managed_credential_details.py new file mode 100644 index 0000000..8e3863e --- /dev/null +++ b/tests/unit/plugins/modules/test_get_managed_credential_details.py @@ -0,0 +1,29 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_managed_credential_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_managed_credential_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_managed_credential_details(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "credential_uuid": "11111", + } + + with patch(BASE_MODULE_PATH + "get_managed_credential_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "credentials/11111", method="GET") diff --git a/tests/unit/plugins/modules/test_get_network_asset_count.py b/tests/unit/plugins/modules/test_get_network_asset_count.py new file mode 100644 index 0000000..c8e65bd --- /dev/null +++ b/tests/unit/plugins/modules/test_get_network_asset_count.py @@ -0,0 +1,35 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_network_asset_count import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_network_asset_count.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_network_asset_count(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "network_id": "123456", + "num_days": 100, + } + + with patch(BASE_MODULE_PATH + "get_network_asset_count.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "networks/123456/counts/assets-not-seen-in/100", method="GET" + ) diff --git a/tests/unit/plugins/modules/test_get_network_details.py b/tests/unit/plugins/modules/test_get_network_details.py new file mode 100644 index 0000000..c3dbc33 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_network_details.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_network_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_network_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_network_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "network_id": "11111", + } + + with patch(BASE_MODULE_PATH + "get_network_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "networks", "11111", method="GET") diff --git a/tests/unit/plugins/modules/test_get_plugin_details.py b/tests/unit/plugins/modules/test_get_plugin_details.py new file mode 100644 index 0000000..35c9170 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_plugin_details.py @@ -0,0 +1,59 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_plugin_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_plugin_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_plugin_details(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "plugin_id": "123456", + "filters": [{"type": "plugin.attributes.vpr.score", "operator": "gte", "value": "6.5"}], + "filter_search_type": "and", + "date_range": "last_7_days", + } + + with patch(BASE_MODULE_PATH + "get_plugin_details.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "get_plugin_details.build_query_parameters" + ) as mock_build_query_parameters: + + def query_params_func(): + return mock_build_query_parameters( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + filters=mock_module.return_value.params["filters"], + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "workbenches/vulnerabilities/123456/info" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_any_call( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + filters=mock_module.return_value.params["filters"], + ) diff --git a/tests/unit/plugins/modules/test_get_report_status.py b/tests/unit/plugins/modules/test_get_report_status.py new file mode 100644 index 0000000..fa9b72e --- /dev/null +++ b/tests/unit/plugins/modules/test_get_report_status.py @@ -0,0 +1,29 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_report_status import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_report_status.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_report_status(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "report_uuid": "123456", + } + + with patch(BASE_MODULE_PATH + "get_report_status.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "reports/export/123456/status", method="GET") diff --git a/tests/unit/plugins/modules/test_get_scan_count.py b/tests/unit/plugins/modules/test_get_scan_count.py new file mode 100644 index 0000000..4cc4846 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_scan_count.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_scan_count import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_scan_count.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_scan_count(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "active": True, + } + + with patch(BASE_MODULE_PATH + "get_scan_count.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scans/count?active=True", method="GET") diff --git a/tests/unit/plugins/modules/test_get_scan_details.py b/tests/unit/plugins/modules/test_get_scan_details.py new file mode 100644 index 0000000..bcb2eff --- /dev/null +++ b/tests/unit/plugins/modules/test_get_scan_details.py @@ -0,0 +1,51 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_scan_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_scan_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_scan_details(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "scan_id": "11111", + "history_id": "12345", + } + + endpoint = "scans/11111" + + with patch(BASE_MODULE_PATH + "get_scan_details.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "get_scan_details.build_query_parameters" + ) as mock_build_query_parameters: + + def query_params_func(): + return mock_build_query_parameters(history_id=mock_module.return_value.params["history_id"]) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == endpoint + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_called_with(history_id=mock_module.return_value.params["history_id"]) diff --git a/tests/unit/plugins/modules/test_get_scan_history.py b/tests/unit/plugins/modules/test_get_scan_history.py new file mode 100644 index 0000000..b5e8227 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_scan_history.py @@ -0,0 +1,33 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_scan_history import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_scan_history.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_scan_history(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "scan_id": "123456", + "exclude_rollover": True, + "limit": 1, + } + + endpoint = "scans/123456/history?exclude_rollover=True" + + with patch(BASE_MODULE_PATH + "get_scan_history.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_get_scan_history_details.py b/tests/unit/plugins/modules/test_get_scan_history_details.py new file mode 100644 index 0000000..dc783a7 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_scan_history_details.py @@ -0,0 +1,30 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_scan_history_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_scan_history_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_scan_history_details(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "scan_id": "123456", + "history_uuid": "23545", + } + + with patch(BASE_MODULE_PATH + "get_scan_history_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scans/123456/history/23545", method="GET") diff --git a/tests/unit/plugins/modules/test_get_scan_progress.py b/tests/unit/plugins/modules/test_get_scan_progress.py new file mode 100644 index 0000000..652517d --- /dev/null +++ b/tests/unit/plugins/modules/test_get_scan_progress.py @@ -0,0 +1,33 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_scan_progress import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_scan_progress.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_scan_progress(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "scan_id": "123456", + "history_id": "123", + "history_uuid": "12345", + } + + endpoint = "scans/123456/progress?history_id=123&history_uuid=12345" + + with patch(BASE_MODULE_PATH + "get_scan_progress.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_get_scanner_details.py b/tests/unit/plugins/modules/test_get_scanner_details.py new file mode 100644 index 0000000..d240981 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_scanner_details.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_scanner_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_scanner_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_scanner_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_id": "123456", + } + + with patch(BASE_MODULE_PATH + "get_scanner_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanners/123456", method="GET") diff --git a/tests/unit/plugins/modules/test_get_scanner_key.py b/tests/unit/plugins/modules/test_get_scanner_key.py new file mode 100644 index 0000000..09c8a12 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_scanner_key.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_scanner_key import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_scanner_key.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_scanner_key(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_id": "123456", + } + + with patch(BASE_MODULE_PATH + "get_scanner_key.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanners/123456/key", method="GET") diff --git a/tests/unit/plugins/modules/test_get_server_status.py b/tests/unit/plugins/modules/test_get_server_status.py new file mode 100644 index 0000000..e7e8959 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_server_status.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_server_status import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_server_status.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_server_status(mock_module): + mock_module.return_value.params = {"access_key": "mock_access_key", "secret_key": "mock_secret_key"} + + endpoint = "server/status" + + with patch(BASE_MODULE_PATH + "get_server_status.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_get_tag_value_details.py b/tests/unit/plugins/modules/test_get_tag_value_details.py new file mode 100644 index 0000000..64f5466 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_tag_value_details.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_tag_value_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_tag_value_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_tag_value_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "value_uuid": "123456", + } + + with patch(BASE_MODULE_PATH + "get_tag_value_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "tags/values/123456", method="GET") diff --git a/tests/unit/plugins/modules/test_get_template_details.py b/tests/unit/plugins/modules/test_get_template_details.py new file mode 100644 index 0000000..ed88b5c --- /dev/null +++ b/tests/unit/plugins/modules/test_get_template_details.py @@ -0,0 +1,65 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_template_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_template_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_template_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "type": "scan", + "wizard_uuid": "12345789", + } + + with patch(BASE_MODULE_PATH + "get_template_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "editor/scan/templates/12345789", method="GET" + ) + + +def test_get_template_details_policy(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "type": "policy", + "wizard_uuid": "987654", + } + + with patch(BASE_MODULE_PATH + "get_template_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "editor/policy/templates/987654", method="GET" + ) + + +def test_get_template_details_remediation(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "type": "remediation", + "wizard_uuid": "654321", + } + + with patch(BASE_MODULE_PATH + "get_template_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "editor/remediation/templates/654321", method="GET" + ) diff --git a/tests/unit/plugins/modules/test_get_timezones.py b/tests/unit/plugins/modules/test_get_timezones.py new file mode 100644 index 0000000..b816814 --- /dev/null +++ b/tests/unit/plugins/modules/test_get_timezones.py @@ -0,0 +1,28 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.get_timezones import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "get_timezones.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_get_timezones(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key"} + + with patch(BASE_MODULE_PATH + "get_timezones.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scans/timezones", method="GET") diff --git a/tests/unit/plugins/modules/test_launch_scan.py b/tests/unit/plugins/modules/test_launch_scan.py new file mode 100644 index 0000000..e1b1f49 --- /dev/null +++ b/tests/unit/plugins/modules/test_launch_scan.py @@ -0,0 +1,39 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.launch_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "launch_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_launch_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + "alt_targets": ["192.168.1.150", "192.168.1.120"], + "rollover": True, + } + + with patch(BASE_MODULE_PATH + "launch_scan.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, + "scans/123456/launch", + method="POST", + data={"alt_targets": ["192.168.1.150", "192.168.1.120"], "rollover": True}, + ) diff --git a/tests/unit/plugins/modules/test_list_agent_filters.py b/tests/unit/plugins/modules/test_list_agent_filters.py new file mode 100644 index 0000000..2da6744 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_agent_filters.py @@ -0,0 +1,28 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_agent_filters import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_agent_filters.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_agent_filters(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key"} + + with patch(BASE_MODULE_PATH + "list_agent_filters.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "filters/scans/agents", method="GET") diff --git a/tests/unit/plugins/modules/test_list_agent_groups.py b/tests/unit/plugins/modules/test_list_agent_groups.py new file mode 100644 index 0000000..54b8b5b --- /dev/null +++ b/tests/unit/plugins/modules/test_list_agent_groups.py @@ -0,0 +1,28 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_agent_groups import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_agent_groups.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_agent_groups(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key"} + + with patch(BASE_MODULE_PATH + "list_agent_groups.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanners/null/agent-groups", method="GET") diff --git a/tests/unit/plugins/modules/test_list_agents.py b/tests/unit/plugins/modules/test_list_agents.py new file mode 100644 index 0000000..ea94d39 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_agents.py @@ -0,0 +1,82 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_agents import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_agents.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_agents(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "filters": [{"type": "platform", "operator": "eq", "value": "LINUX"}], + "wildcard_text": "test", + "wildcard_fields": "name", + "limit": 100, + "offset": 0, + "sort": "asc", + "filter_type": "and", + } + + endpoint = "scanners/null/agents" + + with patch(BASE_MODULE_PATH + "list_agents.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_agents.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_agents.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_agents.handle_special_filter" + ) as mock_handle_special_filter: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + filter_type=mock_module.return_value.params["filter_type"], + wildcard_text=mock_module.return_value.params["wildcard_text"], + wildcard_fields=mock_module.return_value.params["wildcard_fields"], + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ), + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == endpoint + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + + mock_build_query_parameters.assert_any_call( + filter_type=mock_module.return_value.params["filter_type"], + wildcard_text=mock_module.return_value.params["wildcard_text"], + wildcard_fields=mock_module.return_value.params["wildcard_fields"], + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ) + + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) diff --git a/tests/unit/plugins/modules/test_list_agents_by_group.py b/tests/unit/plugins/modules/test_list_agents_by_group.py new file mode 100644 index 0000000..e224f4f --- /dev/null +++ b/tests/unit/plugins/modules/test_list_agents_by_group.py @@ -0,0 +1,80 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_agents_by_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_agents_by_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_agents_by_group(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "agent_group_id": "123456", + "filters": [{"type": "core_version", "operator": "match", "value": "10.6.2"}], + "wildcard_text": "bastion", + "wildcard_fields": "name", + "limit": 100, + "offset": 0, + "sort": "asc", + "filter_type": "and", + } + + with patch(BASE_MODULE_PATH + "list_agents_by_group.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_agents_by_group.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_agents_by_group.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_agents_by_group.handle_special_filter" + ) as mock_handle_special_filter: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + filter_type=mock_module.return_value.params["filter_type"], + wildcard_text=mock_module.return_value.params["wildcard_text"], + wildcard_fields=mock_module.return_value.params["wildcard_fields"], + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ), + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agent-groups/123456/agents" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) + mock_build_query_parameters.assert_any_call( + filter_type=mock_module.return_value.params["filter_type"], + wildcard_text=mock_module.return_value.params["wildcard_text"], + wildcard_fields=mock_module.return_value.params["wildcard_fields"], + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ) + mock_handle_special_filter.assert_not_called() diff --git a/tests/unit/plugins/modules/test_list_asset_filters.py b/tests/unit/plugins/modules/test_list_asset_filters.py new file mode 100644 index 0000000..d821b4c --- /dev/null +++ b/tests/unit/plugins/modules/test_list_asset_filters.py @@ -0,0 +1,28 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_asset_filters import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_asset_filters.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_asset_filters(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key"} + + with patch(BASE_MODULE_PATH + "list_asset_filters.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "filters/workbenches/assets", method="GET") diff --git a/tests/unit/plugins/modules/test_list_asset_filters_vtwo.py b/tests/unit/plugins/modules/test_list_asset_filters_vtwo.py new file mode 100644 index 0000000..14a3710 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_asset_filters_vtwo.py @@ -0,0 +1,32 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_asset_filters_vtwo import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_asset_filters_vtwo.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_asset_filters_vtwo(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "tag_uuids": ["123456", "987654"], + } + + endpoint = "filters/workbenches/assets" + payload = {"tag_uuids": ["123456", "987654"]} + + with patch(BASE_MODULE_PATH + "list_asset_filters_vtwo.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="POST", data=payload) diff --git a/tests/unit/plugins/modules/test_list_asset_tag_filters.py b/tests/unit/plugins/modules/test_list_asset_tag_filters.py new file mode 100644 index 0000000..967be22 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_asset_tag_filters.py @@ -0,0 +1,28 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_asset_tag_filters import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_asset_tag_filters.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_asset_tag_filters(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key"} + + with patch(BASE_MODULE_PATH + "list_asset_tag_filters.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "tags/assets/filters", method="GET") diff --git a/tests/unit/plugins/modules/test_list_asset_vulnerabilities.py b/tests/unit/plugins/modules/test_list_asset_vulnerabilities.py new file mode 100644 index 0000000..d7c2ad7 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_asset_vulnerabilities.py @@ -0,0 +1,73 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_asset_vulnerabilities import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_asset_vulnerabilities.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_asset_vulnerabilities(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "asset_uuid": "123456789", + "date_range": 80, + "filter_search_type": "and", + "filters": [ + {"type": "severity", "operator": "eq", "value": "Info"}, + {"type": "plugin.family_id", "operator": "eq", "value": 23}, + {"type": "tracking.state", "operator": "eq", "value": "Active"}, + ], + } + + with patch(BASE_MODULE_PATH + "list_asset_vulnerabilities.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_asset_vulnerabilities.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_asset_vulnerabilities.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_asset_vulnerabilities.handle_multiple_filters" + ) as mock_handle_multiple_filters: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ), + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "workbenches/assets/123456789/vulnerabilities" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + mock_build_query_parameters.assert_any_call( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ) + mock_handle_multiple_filters.assert_not_called() diff --git a/tests/unit/plugins/modules/test_list_asset_vulnerabilities_for_plugin.py b/tests/unit/plugins/modules/test_list_asset_vulnerabilities_for_plugin.py new file mode 100644 index 0000000..d8e321f --- /dev/null +++ b/tests/unit/plugins/modules/test_list_asset_vulnerabilities_for_plugin.py @@ -0,0 +1,73 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_asset_vulnerabilities_for_plugin import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_asset_vulnerabilities_for_plugin.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_asset_vulnerabilities_for_plugin(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "asset_id": "123456", + "plugin_id": "987654", + "date_range": 5, + "filters": [{"type": "plugin.name", "operator": "match", "value": "RHEL"}], + "filter_search_type": "or", + } + + endpoint = "workbenches/assets/123456/vulnerabilities/987654/outputs" + + with patch(BASE_MODULE_PATH + "list_asset_vulnerabilities_for_plugin.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_asset_vulnerabilities_for_plugin.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_asset_vulnerabilities_for_plugin.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_asset_vulnerabilities_for_plugin.handle_multiple_filters" + ) as mock_handle_multiple_filters: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ), + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == endpoint + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_any_call( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ) + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) diff --git a/tests/unit/plugins/modules/test_list_asset_with_vulnerabilities.py b/tests/unit/plugins/modules/test_list_asset_with_vulnerabilities.py new file mode 100644 index 0000000..fe33e9f --- /dev/null +++ b/tests/unit/plugins/modules/test_list_asset_with_vulnerabilities.py @@ -0,0 +1,71 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_asset_with_vulnerabilities import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_asset_with_vulnerabilities.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_asset_with_vulnerabilities(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "date_range": 1, + "filter_search_type": "and", + "filters": [ + {"type": "severity", "operator": "eq", "value": "Critical"}, + {"type": "tag.Project", "operator": "set-has", "value": "Project Name"}, + ], + } + + endpoint = "workbenches/assets/vulnerabilities" + + with patch(BASE_MODULE_PATH + "list_asset_with_vulnerabilities.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_asset_with_vulnerabilities.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_asset_with_vulnerabilities.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_asset_with_vulnerabilities.handle_multiple_filters" + ) as mock_handle_multiple_filters: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ), + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == endpoint + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_called_with( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ) + + mock_add_custom_filters diff --git a/tests/unit/plugins/modules/test_list_assets.py b/tests/unit/plugins/modules/test_list_assets.py new file mode 100644 index 0000000..00ffb15 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_assets.py @@ -0,0 +1,73 @@ +# (c) 2024, Fernando Mendieta Ovejero (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_assets import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_assets.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_assets(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "date_range": 7, + "filter_search_type": "or", + "all_fields": "full", + "filters": [{"type": "aws_ec2_instance_id", "operator": "eq", "value": "i-123456789"}], + } + + with patch(BASE_MODULE_PATH + "list_assets.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_assets.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_assets.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_assets.handle_multiple_filters" + ) as mock_handle_multiple_filters: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + all_fields=mock_module.return_value.params["all_fields"], + ), + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "workbenches/assets" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + mock_build_query_parameters.assert_any_call( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + all_fields=mock_module.return_value.params["all_fields"], + ) + mock_handle_multiple_filters.assert_not_called() diff --git a/tests/unit/plugins/modules/test_list_assignable_scanners.py b/tests/unit/plugins/modules/test_list_assignable_scanners.py new file mode 100644 index 0000000..b2ec4f6 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_assignable_scanners.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_assignable_scanners import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_assignable_scanners.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_assignable_scanners(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "network_id": "012345", + } + + endpoint = "networks/012345/assignable-scanners" + + with patch(BASE_MODULE_PATH + "list_assignable_scanners.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_attributes.py b/tests/unit/plugins/modules/test_list_attributes.py new file mode 100644 index 0000000..ab18758 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_attributes.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_attributes import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_attributes.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_attributes(mock_module): + mock_module.return_value.params = {"access_key": "mock_access_key", "secret_key": "mock_secret_key"} + + endpoint = "api/v3/assets/attributes" + + with patch(BASE_MODULE_PATH + "list_attributes.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_attributes_assigned_to_asset.py b/tests/unit/plugins/modules/test_list_attributes_assigned_to_asset.py new file mode 100644 index 0000000..c69b49e --- /dev/null +++ b/tests/unit/plugins/modules/test_list_attributes_assigned_to_asset.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_attributes_assigned_to_asset import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_attributes_assigned_to_asset.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_attributes_assigned_to_asset(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "asset_uuid": "12345", + } + + endpoint = "api/v3/assets/12345/attributes" + + with patch(BASE_MODULE_PATH + "list_attributes_assigned_to_asset.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_aws_scan_targets.py b/tests/unit/plugins/modules/test_list_aws_scan_targets.py new file mode 100644 index 0000000..8517518 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_aws_scan_targets.py @@ -0,0 +1,31 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_aws_scan_targets import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_aws_scan_targets.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_aws_scan_targets(mock_module): + mock_module.return_value.params = { + "access_key": "mock_access_key", + "secret_key": "mock_secret_key", + "scanner_id": "11111", + } + + endpoint = "scanners/11111/aws-targets" + + with patch(BASE_MODULE_PATH + "list_aws_scan_targets.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_credential_filters.py b/tests/unit/plugins/modules/test_list_credential_filters.py new file mode 100644 index 0000000..cde7845 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_credential_filters.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_credential_filters import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_credential_filters.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_credential_filters(mock_module): + mock_module.return_value.params = {"access_key": "mock_access_key", "secret_key": "mock_secret_key"} + + endpoint = "filters/credentials" + + with patch(BASE_MODULE_PATH + "list_credential_filters.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_credential_types.py b/tests/unit/plugins/modules/test_list_credential_types.py new file mode 100644 index 0000000..f1fc5f1 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_credential_types.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_credential_types import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_credential_types.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_credential_types(mock_module): + mock_module.return_value.params = {"access_key": "mock_access_key", "secret_key": "mock_secret_key"} + + endpoint = "credentials/types" + + with patch(BASE_MODULE_PATH + "list_credential_types.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_exclusions.py b/tests/unit/plugins/modules/test_list_exclusions.py new file mode 100644 index 0000000..1a78262 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_exclusions.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_exclusions import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_exclusions.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_exclusions(mock_module): + mock_module.return_value.params = {"access_key": "mock_access_key", "secret_key": "mock_secret_key"} + + endpoint = "exclusions" + + with patch(BASE_MODULE_PATH + "list_exclusions.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_folders.py b/tests/unit/plugins/modules/test_list_folders.py new file mode 100644 index 0000000..955018d --- /dev/null +++ b/tests/unit/plugins/modules/test_list_folders.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_folders import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_folders.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_folders(mock_module): + mock_module.return_value.params = {"access_key": "mock_access_key", "secret_key": "mock_secret_key"} + + endpoint = "folders" + + with patch(BASE_MODULE_PATH + "list_folders.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_groups.py b/tests/unit/plugins/modules/test_list_groups.py new file mode 100644 index 0000000..88978e7 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_groups.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_groups import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_groups.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_groups(mock_module): + mock_module.return_value.params = {"access_key": "mock_access_key", "secret_key": "mock_secret_key"} + + endpoint = "groups" + + with patch(BASE_MODULE_PATH + "list_groups.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, endpoint, method="GET") diff --git a/tests/unit/plugins/modules/test_list_networks.py b/tests/unit/plugins/modules/test_list_networks.py new file mode 100644 index 0000000..db70d9f --- /dev/null +++ b/tests/unit/plugins/modules/test_list_networks.py @@ -0,0 +1,73 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_networks import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_networks.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_networks(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "filters": [{"type": "name", "operator": "eq", "value": "aws_network_1"}], + "limit": 20, + "offset": 0, + "sort": "name:asc", + "include_deleted": False, + } + + with patch(BASE_MODULE_PATH + "list_networks.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_networks.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_networks.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_networks.handle_special_filter" + ) as mock_handle_special_filter: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + ), + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "networks" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + + mock_build_query_parameters.assert_any_call( + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + ) + + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) diff --git a/tests/unit/plugins/modules/test_list_plugin_families.py b/tests/unit/plugins/modules/test_list_plugin_families.py new file mode 100644 index 0000000..e804c82 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_plugin_families.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_plugin_families import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_plugin_families.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_plugin_families(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key", "all": True} + + with patch(BASE_MODULE_PATH + "list_plugin_families.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "plugins/families?all=/True" + assert called_kwargs["method"] == "GET" diff --git a/tests/unit/plugins/modules/test_list_plugin_in_familiy_id.py b/tests/unit/plugins/modules/test_list_plugin_in_familiy_id.py new file mode 100644 index 0000000..a70d314 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_plugin_in_familiy_id.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_plugin_in_familiy_id import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_plugin_in_familiy_id.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_plugin_in_familiy_id(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key", "id": "123456"} + + with patch(BASE_MODULE_PATH + "list_plugin_in_familiy_id.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "plugins/families/123456" + assert called_kwargs["method"] == "GET" diff --git a/tests/unit/plugins/modules/test_list_plugin_outputs.py b/tests/unit/plugins/modules/test_list_plugin_outputs.py new file mode 100644 index 0000000..ba36bee --- /dev/null +++ b/tests/unit/plugins/modules/test_list_plugin_outputs.py @@ -0,0 +1,70 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_plugin_outputs import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_plugin_outputs.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_plugin_outputs(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "plugin_id": 123456, + "filters": [{"type": "port.protocol", "operator": "eq", "value": "tcp"}], + "date_range": None, + "filter_search_type": None, + } + + with patch(BASE_MODULE_PATH + "list_plugin_outputs.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_plugin_outputs.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_plugin_outputs.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_plugin_outputs.handle_multiple_filters" + ) as mock_handle_multiple_filters: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ), + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "workbenches/vulnerabilities/123456/outputs" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_any_call( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ) + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) diff --git a/tests/unit/plugins/modules/test_list_plugins.py b/tests/unit/plugins/modules/test_list_plugins.py new file mode 100644 index 0000000..378b3cc --- /dev/null +++ b/tests/unit/plugins/modules/test_list_plugins.py @@ -0,0 +1,59 @@ +# (c) 2024, Fernando Mendieta Ovejero (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_plugins import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_plugins.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_plugins(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "last_updated": "2024-01-01", + "size": 10, + "page": 1, + } + + with patch(BASE_MODULE_PATH + "list_plugins.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_plugins.build_query_parameters" + ) as mock_build_query_parameters: + + def query_params_func(): + return mock_build_query_parameters( + last_updated=mock_module.return_value.params["last_updated"], + size=mock_module.return_value.params["size"], + page=mock_module.return_value.params["page"], + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "plugins/plugin" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + + mock_build_query_parameters.assert_any_call( + last_updated=mock_module.return_value.params["last_updated"], + size=mock_module.return_value.params["size"], + page=mock_module.return_value.params["page"], + ) diff --git a/tests/unit/plugins/modules/test_list_plugins_in_family_name.py b/tests/unit/plugins/modules/test_list_plugins_in_family_name.py new file mode 100644 index 0000000..5f0e3bc --- /dev/null +++ b/tests/unit/plugins/modules/test_list_plugins_in_family_name.py @@ -0,0 +1,44 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_plugins_in_family_name import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_plugins_in_family_name.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_plugins_in_family_name(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "name": "Linux", + } + + with patch(BASE_MODULE_PATH + "list_plugins_in_family_name.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_plugins_in_family_name.build_payload" + ) as mock_build_payload: + mock_build_payload.return_value = {"name": "Linux"} # Simulate the payload + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "plugins/families/_byName" + assert called_kwargs["method"] == "POST" + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) + + assert called_kwargs["data"] == {"name": "Linux"} diff --git a/tests/unit/plugins/modules/test_list_policies.py b/tests/unit/plugins/modules/test_list_policies.py new file mode 100644 index 0000000..0b179cc --- /dev/null +++ b/tests/unit/plugins/modules/test_list_policies.py @@ -0,0 +1,28 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_policies import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_policies.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_policies(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key"} + + with patch(BASE_MODULE_PATH + "list_policies.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "policies", method="GET") diff --git a/tests/unit/plugins/modules/test_list_remediation_scans.py b/tests/unit/plugins/modules/test_list_remediation_scans.py new file mode 100644 index 0000000..d77a87b --- /dev/null +++ b/tests/unit/plugins/modules/test_list_remediation_scans.py @@ -0,0 +1,58 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_remediation_scans import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_remediation_scans.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_remediation_scans(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "limit": 10, + "offset": 0, + "sort": "asc", + } + + with patch(BASE_MODULE_PATH + "list_remediation_scans.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_remediation_scans.build_query_parameters" + ) as mock_build_query_parameters: + + def query_params_func(): + return mock_build_query_parameters( + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scans/remediation" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_called_with( + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ) diff --git a/tests/unit/plugins/modules/test_list_report_filters.py b/tests/unit/plugins/modules/test_list_report_filters.py new file mode 100644 index 0000000..af23c29 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_report_filters.py @@ -0,0 +1,28 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_report_filters import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_report_filters.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_report_filters(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key"} + + with patch(BASE_MODULE_PATH + "list_report_filters.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "filters/reports/export", method="GET") diff --git a/tests/unit/plugins/modules/test_list_running_scans.py b/tests/unit/plugins/modules/test_list_running_scans.py new file mode 100644 index 0000000..000ca9e --- /dev/null +++ b/tests/unit/plugins/modules/test_list_running_scans.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_running_scans import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_running_scans.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_running_scans(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_id": "11111", + } + + with patch(BASE_MODULE_PATH + "list_running_scans.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanners/11111/scans", method="GET") diff --git a/tests/unit/plugins/modules/test_list_scan_filters.py b/tests/unit/plugins/modules/test_list_scan_filters.py new file mode 100644 index 0000000..e565495 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_scan_filters.py @@ -0,0 +1,31 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_scan_filters import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_scan_filters.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_scan_filters(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + } + + with patch(BASE_MODULE_PATH + "list_scan_filters.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "filters/scans/reports", method="GET") diff --git a/tests/unit/plugins/modules/test_list_scan_history_filters.py b/tests/unit/plugins/modules/test_list_scan_history_filters.py new file mode 100644 index 0000000..e42f0e4 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_scan_history_filters.py @@ -0,0 +1,31 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_scan_history_filters import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_scan_history_filters.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_scan_history_filters(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + } + + with patch(BASE_MODULE_PATH + "list_scan_history_filters.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "filters/scans/reports/history", method="GET") diff --git a/tests/unit/plugins/modules/test_list_scan_routes.py b/tests/unit/plugins/modules/test_list_scan_routes.py new file mode 100644 index 0000000..f1a5ef4 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_scan_routes.py @@ -0,0 +1,32 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_scan_routes import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_scan_routes.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_scan_routes(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 124, + } + + with patch(BASE_MODULE_PATH + "list_scan_routes.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanner-groups/124/routes", method="GET") diff --git a/tests/unit/plugins/modules/test_list_scanner_group_details.py b/tests/unit/plugins/modules/test_list_scanner_group_details.py new file mode 100644 index 0000000..6f6d538 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_scanner_group_details.py @@ -0,0 +1,32 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_scanner_group_details import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_scanner_group_details.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_scanner_group_details(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 12345, + } + + with patch(BASE_MODULE_PATH + "list_scanner_group_details.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanner-groups/12345", method="GET") diff --git a/tests/unit/plugins/modules/test_list_scanner_groups.py b/tests/unit/plugins/modules/test_list_scanner_groups.py new file mode 100644 index 0000000..fe01e03 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_scanner_groups.py @@ -0,0 +1,31 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_scanner_groups import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_scanner_groups.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_scanner_groups(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + } + + with patch(BASE_MODULE_PATH + "list_scanner_groups.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanner-groups", method="GET") diff --git a/tests/unit/plugins/modules/test_list_scanners.py b/tests/unit/plugins/modules/test_list_scanners.py new file mode 100644 index 0000000..95ce48c --- /dev/null +++ b/tests/unit/plugins/modules/test_list_scanners.py @@ -0,0 +1,31 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_scanners import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_scanners.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_scanners(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + } + + with patch(BASE_MODULE_PATH + "list_scanners.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanners", method="GET") diff --git a/tests/unit/plugins/modules/test_list_scanners_within_scanner_group.py b/tests/unit/plugins/modules/test_list_scanners_within_scanner_group.py new file mode 100644 index 0000000..e9064c8 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_scanners_within_scanner_group.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_scanners_within_scanner_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_scanners_within_scanner_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_scanners_within_scanner_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 12345, + } + + with patch(BASE_MODULE_PATH + "list_scanners_within_scanner_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scanner-groups/12345/scanners", method="GET") diff --git a/tests/unit/plugins/modules/test_list_scans.py b/tests/unit/plugins/modules/test_list_scans.py new file mode 100644 index 0000000..19baf8d --- /dev/null +++ b/tests/unit/plugins/modules/test_list_scans.py @@ -0,0 +1,51 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_scans import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_scans.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_scans(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "folder_id": 123456, + "last_modification_date": 1234567890, + } + + with patch(BASE_MODULE_PATH + "list_scans.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_scans.build_query_parameters" + ) as mock_build_query_parameters: + + def query_params_func(): + return mock_build_query_parameters( + folder_id=mock_module.return_value.params["folder_id"], + last_modification_date=mock_module.return_value.params["last_modification_date"], + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scans" + assert called_kwargs["method"] == "GET" + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_build_query_parameters.assert_any_call( + folder_id=mock_module.return_value.params["folder_id"], + last_modification_date=mock_module.return_value.params["last_modification_date"], + ) diff --git a/tests/unit/plugins/modules/test_list_tag_categories.py b/tests/unit/plugins/modules/test_list_tag_categories.py new file mode 100644 index 0000000..e505782 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_tag_categories.py @@ -0,0 +1,75 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_tag_categories import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_tag_categories.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_tag_categories(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "limit": 1, + "offset": 0, + "sort": "asc", + "filters": [{"type": "name", "operator": "eq", "value": "test"}], + "filter_type": "and", + } + + with patch(BASE_MODULE_PATH + "list_tag_categories.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_tag_categories.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_tag_categories.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_tag_categories.handle_special_filter" + ) as mock_handle_special_filter: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + filter_type=mock_module.return_value.params["filter_type"], + ), + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "tags/categories" + assert called_kwargs["method"] == "GET" + + query_params = query_params_func() + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) + mock_build_query_parameters.assert_any_call( + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + filter_type=mock_module.return_value.params["filter_type"], + ) + mock_handle_special_filter.assert_not_called() + actual_query_params_func = called_kwargs["query_params_func"] + assert actual_query_params_func() == query_params diff --git a/tests/unit/plugins/modules/test_list_tag_values.py b/tests/unit/plugins/modules/test_list_tag_values.py new file mode 100644 index 0000000..a006023 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_tag_values.py @@ -0,0 +1,83 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_tag_values import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_tag_values.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_tag_values(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "filter_type": "and", + "filters": [{"type": "value", "operator": "eq", "value": "test"}], + "wildcard_fields": "category_name", + "wildcard_text": "finance", + "limit": 1, + "offset": 0, + "sort": "asc", + } + + with patch(BASE_MODULE_PATH + "list_tag_values.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_tag_values.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_tag_values.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_tag_values.handle_special_filter" + ) as mock_handle_special_filter: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + filter_type=mock_module.return_value.params["filter_type"], + wildcard_text=mock_module.return_value.params["wildcard_text"], + wildcard_fields=mock_module.return_value.params["wildcard_fields"], + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ), + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "tags/values" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + + mock_build_query_parameters.assert_any_call( + filter_type=mock_module.return_value.params["filter_type"], + wildcard_text=mock_module.return_value.params["wildcard_text"], + wildcard_fields=mock_module.return_value.params["wildcard_fields"], + limit=mock_module.return_value.params["limit"], + offset=mock_module.return_value.params["offset"], + sort=mock_module.return_value.params["sort"], + ) + + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_special_filter, + ) diff --git a/tests/unit/plugins/modules/test_list_tags_for_an_asset.py b/tests/unit/plugins/modules/test_list_tags_for_an_asset.py new file mode 100644 index 0000000..f923c95 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_tags_for_an_asset.py @@ -0,0 +1,34 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_tags_for_an_asset import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_tags_for_an_asset.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_tags_for_an_asset(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "asset_uuid": "123456", + } + + with patch(BASE_MODULE_PATH + "list_tags_for_an_asset.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "tags/assets/123456/assignments", method="GET" + ) diff --git a/tests/unit/plugins/modules/test_list_templates.py b/tests/unit/plugins/modules/test_list_templates.py new file mode 100644 index 0000000..3c23fa1 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_templates.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_templates import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_templates.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_templates(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "type": "scan", + } + + with patch(BASE_MODULE_PATH + "list_templates.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "editor/scan/templates", method="GET") diff --git a/tests/unit/plugins/modules/test_list_users_in_group.py b/tests/unit/plugins/modules/test_list_users_in_group.py new file mode 100644 index 0000000..41a79df --- /dev/null +++ b/tests/unit/plugins/modules/test_list_users_in_group.py @@ -0,0 +1,32 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_users_in_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_users_in_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_users_in_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 1233, + } + + with patch(BASE_MODULE_PATH + "list_users_in_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "groups/1233/users", method="GET") diff --git a/tests/unit/plugins/modules/test_list_vulnerabilities.py b/tests/unit/plugins/modules/test_list_vulnerabilities.py new file mode 100644 index 0000000..0898378 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_vulnerabilities.py @@ -0,0 +1,69 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_vulnerabilities import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_vulnerabilities.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_vulnerabilities(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "filters": [{"type": "plugin.attributes.vpr.score", "operator": "eq", "value": "5"}], + "filter_search_type": "and", + "date_range": 30, + } + + with patch(BASE_MODULE_PATH + "list_vulnerabilities.run_module") as mock_run_module, patch( + BASE_MODULE_PATH + "list_vulnerabilities.add_custom_filters" + ) as mock_add_custom_filters, patch( + BASE_MODULE_PATH + "list_vulnerabilities.build_query_parameters" + ) as mock_build_query_parameters, patch( + BASE_MODULE_PATH + "list_vulnerabilities.handle_multiple_filters" + ) as mock_handle_multiple_filters: + + def query_params_func(): + return mock_add_custom_filters( + mock_build_query_parameters( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ), + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + + main() + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "workbenches/vulnerabilities" + assert called_kwargs["method"] == "GET" + + expected_query_params = query_params_func() + actual_query_params = called_kwargs["query_params_func"]() + + assert expected_query_params == actual_query_params + mock_add_custom_filters.assert_any_call( + mock_build_query_parameters.return_value, + mock_module.return_value.params["filters"], + mock_handle_multiple_filters, + ) + mock_build_query_parameters.assert_any_call( + date_range=mock_module.return_value.params["date_range"], + filter_search_type=mock_module.return_value.params["filter_search_type"], + ) diff --git a/tests/unit/plugins/modules/test_list_vulnerability_filters.py b/tests/unit/plugins/modules/test_list_vulnerability_filters.py new file mode 100644 index 0000000..dffe6a3 --- /dev/null +++ b/tests/unit/plugins/modules/test_list_vulnerability_filters.py @@ -0,0 +1,30 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_vulnerability_filters import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_vulnerability_filters.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_vulnerability_filters(mock_module): + mock_module.return_value.params = {"access_key": "fake_access_key", "secret_key": "fake_secret_key"} + + with patch(BASE_MODULE_PATH + "list_vulnerability_filters.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "filters/workbenches/vulnerabilities", method="GET" + ) diff --git a/tests/unit/plugins/modules/test_list_vulnerability_filters_vtwo.py b/tests/unit/plugins/modules/test_list_vulnerability_filters_vtwo.py new file mode 100644 index 0000000..7c58a0e --- /dev/null +++ b/tests/unit/plugins/modules/test_list_vulnerability_filters_vtwo.py @@ -0,0 +1,44 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.list_vulnerability_filters_vtwo import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "list_vulnerability_filters_vtwo.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_list_vulnerability_filters_vtwo(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "tag_uuids": ["123456", "987654"], + } + + expected_payload = {"tag_uuids": ["123456", "987654"]} + + with patch( + BASE_MODULE_PATH + "list_vulnerability_filters_vtwo.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "list_vulnerability_filters_vtwo.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "filters/workbenches/vulnerabilities" + assert called_kwargs["method"] == "POST" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["tag_uuids"]) diff --git a/tests/unit/plugins/modules/test_move_assets.py b/tests/unit/plugins/modules/test_move_assets.py new file mode 100644 index 0000000..e9c87ec --- /dev/null +++ b/tests/unit/plugins/modules/test_move_assets.py @@ -0,0 +1,43 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.move_assets import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "move_assets.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_move_assets(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "source": "123456", + "destination": "654321", + "targets": "192.168.1.120", + } + + with patch(BASE_MODULE_PATH + "move_assets.build_payload") as mock_build_payload: + mock_build_payload.return_value = {"source": "123456", "destination": "654321", "targets": "192.168.1.120"} + + with patch(BASE_MODULE_PATH + "move_assets.run_module") as mock_run_module: + main() + mock_build_payload.assert_called_once_with(mock_module.return_value, ["source", "destination", "targets"]) + mock_run_module.assert_called_once_with( + mock_module.return_value, + "api/v2/assets/bulk-jobs/move-to-network", + method="POST", + data={"source": "123456", "destination": "654321", "targets": "192.168.1.120"}, + ) diff --git a/tests/unit/plugins/modules/test_pause_scan.py b/tests/unit/plugins/modules/test_pause_scan.py new file mode 100644 index 0000000..62873af --- /dev/null +++ b/tests/unit/plugins/modules/test_pause_scan.py @@ -0,0 +1,32 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.pause_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "pause_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_pause_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + } + + with patch(BASE_MODULE_PATH + "pause_scan.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with(mock_module.return_value, "scans/123456/pause", method="POST") diff --git a/tests/unit/plugins/modules/test_remove_agent_from_group.py b/tests/unit/plugins/modules/test_remove_agent_from_group.py new file mode 100644 index 0000000..6555ded --- /dev/null +++ b/tests/unit/plugins/modules/test_remove_agent_from_group.py @@ -0,0 +1,35 @@ +# (c) 2024, @valkiriaaquatica (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.remove_agent_from_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "remove_agent_from_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_remove_agent_from_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": "123456", + "agent_id": "654321", + } + + with patch(BASE_MODULE_PATH + "remove_agent_from_group.run_module") as mock_run_module: + main() + mock_run_module.assert_called_once_with( + mock_module.return_value, "scanners/null/agent-groups/123456/agents/654321", method="DELETE" + ) diff --git a/tests/unit/plugins/modules/test_remove_agents_from_network.py b/tests/unit/plugins/modules/test_remove_agents_from_network.py new file mode 100644 index 0000000..5e1926b --- /dev/null +++ b/tests/unit/plugins/modules/test_remove_agents_from_network.py @@ -0,0 +1,66 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.remove_agents_from_network import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "remove_agents_from_network.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_remove_agents_from_network(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "network_uuid": "123456789", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + } + + expected_payload = { + "network_uuid": "123456789", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + } + + with patch( + BASE_MODULE_PATH + "remove_agents_from_network.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "remove_agents_from_network.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agents/_bulk/removeFromNetwork" + assert called_kwargs["method"] == "POST" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["network_uuid", "criteria", "items", "not_items"] + ) diff --git a/tests/unit/plugins/modules/test_remove_scanner_from_scanner_group.py b/tests/unit/plugins/modules/test_remove_scanner_from_scanner_group.py new file mode 100644 index 0000000..aa38031 --- /dev/null +++ b/tests/unit/plugins/modules/test_remove_scanner_from_scanner_group.py @@ -0,0 +1,38 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.remove_scanner_from_scanner_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "remove_scanner_from_scanner_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_remove_scanner_from_scanner_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 12345, + "scanner_id": 67890, + } + + with patch(BASE_MODULE_PATH + "remove_scanner_from_scanner_group.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanner-groups/12345/scanners/67890" + assert called_kwargs["method"] == "DELETE" diff --git a/tests/unit/plugins/modules/test_rename_agent.py b/tests/unit/plugins/modules/test_rename_agent.py new file mode 100644 index 0000000..a9fa804 --- /dev/null +++ b/tests/unit/plugins/modules/test_rename_agent.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.rename_agent import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "rename_agent.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_rename_agent(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "agent_id": "123456789", + "name": "new_name", + } + + expected_payload = {"name": "new_name"} + + with patch( + BASE_MODULE_PATH + "rename_agent.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "rename_agent.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agents/123456789" + assert called_kwargs["method"] == "PATCH" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) diff --git a/tests/unit/plugins/modules/test_rename_folder.py b/tests/unit/plugins/modules/test_rename_folder.py new file mode 100644 index 0000000..c6c6fda --- /dev/null +++ b/tests/unit/plugins/modules/test_rename_folder.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.rename_folder import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "rename_folder.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_rename_folder(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "folder_id": 12345, + "name": "new_name", + } + + expected_payload = {"name": "new_name"} + + with patch( + BASE_MODULE_PATH + "rename_folder.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "rename_folder.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "folders/12345" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) diff --git a/tests/unit/plugins/modules/test_resume_scan.py b/tests/unit/plugins/modules/test_resume_scan.py new file mode 100644 index 0000000..7887f0c --- /dev/null +++ b/tests/unit/plugins/modules/test_resume_scan.py @@ -0,0 +1,37 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.resume_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "resume_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_resume_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + } + + with patch(BASE_MODULE_PATH + "resume_scan.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scans/123456/resume" + assert called_kwargs["method"] == "POST" diff --git a/tests/unit/plugins/modules/test_send_instructions_to_agents.py b/tests/unit/plugins/modules/test_send_instructions_to_agents.py new file mode 100644 index 0000000..bc3c937 --- /dev/null +++ b/tests/unit/plugins/modules/test_send_instructions_to_agents.py @@ -0,0 +1,66 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.send_instructions_to_agents import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "send_instructions_to_agents.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_send_instructions_to_agents(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "criteria": { + "all_agents": True, + "wildcard": "wildcard", + "filters": ["core_version:lt:10.0.0"], + "filter_type": "and", + "hardcoded_filters": ["hardcoded_filters"], + }, + "directive": {"type": "restart", "options": {"hard": True, "idle": False}}, + "items": ["12345"], + "not_items": ["98765"], + } + + expected_payload = { + "criteria": { + "all_agents": True, + "wildcard": "wildcard", + "filters": ["core_version:lt:10.0.0"], + "filter_type": "and", + "hardcoded_filters": ["hardcoded_filters"], + }, + "items": ["12345"], + "not_items": ["98765"], + "directive": {"type": "restart", "options": {"hard": True, "idle": False}}, + } + + with patch( + BASE_MODULE_PATH + "send_instructions_to_agents.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "send_instructions_to_agents.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agents/_bulk/directive" + assert called_kwargs["method"] == "POST" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["criteria", "items", "not_items", "directive"] + ) diff --git a/tests/unit/plugins/modules/test_send_instructions_to_agents_group.py b/tests/unit/plugins/modules/test_send_instructions_to_agents_group.py new file mode 100644 index 0000000..9640818 --- /dev/null +++ b/tests/unit/plugins/modules/test_send_instructions_to_agents_group.py @@ -0,0 +1,69 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.send_instructions_to_agents_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "send_instructions_to_agents_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_send_instructions_to_agents_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": "123456", + "criteria": { + "all_agents": True, + "wildcard": "wildcard", + "filters": ["core_version:lt:10.0.0"], + "filter_type": "and", + "hardcoded_filters": ["hardcoded_filters"], + }, + "directive": {"type": "restart", "options": {"hard": True, "idle": False}}, + "items": ["12345"], + "not_items": ["98765"], + } + + expected_payload = { + "criteria": { + "all_agents": True, + "wildcard": "wildcard", + "filters": ["core_version:lt:10.0.0"], + "filter_type": "and", + "hardcoded_filters": ["hardcoded_filters"], + }, + "items": ["12345"], + "not_items": ["98765"], + "directive": {"type": "restart", "options": {"hard": True, "idle": False}}, + } + + with patch( + BASE_MODULE_PATH + "send_instructions_to_agents_group.build_payload", return_value=expected_payload + ) as mock_build_payload, patch( + BASE_MODULE_PATH + "send_instructions_to_agents_group.run_module" + ) as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agent-groups/123456/agents/_bulk/directive" + assert called_kwargs["method"] == "POST" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["criteria", "items", "not_items", "directive"] + ) diff --git a/tests/unit/plugins/modules/test_send_instructions_to_multiple_scanners.py b/tests/unit/plugins/modules/test_send_instructions_to_multiple_scanners.py new file mode 100644 index 0000000..284c252 --- /dev/null +++ b/tests/unit/plugins/modules/test_send_instructions_to_multiple_scanners.py @@ -0,0 +1,52 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.send_instructions_to_multiple_scanners import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "send_instructions_to_multiple_scanners.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_send_instructions_to_multiple_scanners(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "all_scanners": True, + "scanners": ["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + "directive": {"type": "restart", "restart": {"hard": True, "idle": False}}, + } + + expected_payload = { + "all_scanners": True, + "scanners": ["123e4567-e89b-12d3-a456-426614174000", "123e4567-e89b-12d3-a456-426614174001"], + "directive": {"type": "restart", "restart": {"hard": True, "idle": False}}, + } + + with patch( + BASE_MODULE_PATH + "send_instructions_to_multiple_scanners.build_payload", return_value=expected_payload + ) as mock_build_payload, patch( + BASE_MODULE_PATH + "send_instructions_to_multiple_scanners.run_module" + ) as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/directive" + assert called_kwargs["method"] == "POST" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["all_scanners", "scanners", "directive"]) diff --git a/tests/unit/plugins/modules/test_send_instructions_to_scanner.py b/tests/unit/plugins/modules/test_send_instructions_to_scanner.py new file mode 100644 index 0000000..1020e58 --- /dev/null +++ b/tests/unit/plugins/modules/test_send_instructions_to_scanner.py @@ -0,0 +1,46 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.send_instructions_to_scanner import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "send_instructions_to_scanner.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_send_instructions_to_scanner(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_uuid": "123e4567-e89b-12d3-a456-426614174000", + "type": "restart", + "restart": {"hard": True, "idle": False}, + } + + expected_payload = {"type": "restart", "restart": {"hard": True, "idle": False}} + + with patch( + BASE_MODULE_PATH + "send_instructions_to_scanner.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "send_instructions_to_scanner.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/123e4567-e89b-12d3-a456-426614174000/directive" + assert called_kwargs["method"] == "POST" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["type", "restart", "settings"]) diff --git a/tests/unit/plugins/modules/test_stop_scan.py b/tests/unit/plugins/modules/test_stop_scan.py new file mode 100644 index 0000000..1a2faa1 --- /dev/null +++ b/tests/unit/plugins/modules/test_stop_scan.py @@ -0,0 +1,37 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.stop_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "stop_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_stop_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + } + + with patch(BASE_MODULE_PATH + "stop_scan.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scans/123456/stop" + assert called_kwargs["method"] == "POST" diff --git a/tests/unit/plugins/modules/test_toggle_scanner_link_state.py b/tests/unit/plugins/modules/test_toggle_scanner_link_state.py new file mode 100644 index 0000000..7f08b5a --- /dev/null +++ b/tests/unit/plugins/modules/test_toggle_scanner_link_state.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.toggle_scanner_link_state import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "toggle_scanner_link_state.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_toggle_scanner_link_state(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_id": 123456, + "link": 1, + } + + expected_payload = {"link": 1} + + with patch( + BASE_MODULE_PATH + "toggle_scanner_link_state.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "toggle_scanner_link_state.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/123456/link" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["link"]) diff --git a/tests/unit/plugins/modules/test_unlink_agent.py b/tests/unit/plugins/modules/test_unlink_agent.py new file mode 100644 index 0000000..b2ae169 --- /dev/null +++ b/tests/unit/plugins/modules/test_unlink_agent.py @@ -0,0 +1,37 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.unlink_agent import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "unlink_agent.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_unlink_agent(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "agent_id": 11111, + } + + with patch(BASE_MODULE_PATH + "unlink_agent.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agents/11111" + assert called_kwargs["method"] == "DELETE" diff --git a/tests/unit/plugins/modules/test_unlink_agents.py b/tests/unit/plugins/modules/test_unlink_agents.py new file mode 100644 index 0000000..8778409 --- /dev/null +++ b/tests/unit/plugins/modules/test_unlink_agents.py @@ -0,0 +1,62 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.unlink_agents import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "unlink_agents.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_unlink_agents(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + } + + expected_payload = { + "criteria": { + "filters": ["name:match:laptop"], + "all_agents": True, + "wildcard": "wildcard", + "filter_type": "and", + "hardcoded_filters": ["name:match:office"], + }, + "items": ["12345", "65789"], + "not_items": ["98765"], + } + + with patch( + BASE_MODULE_PATH + "unlink_agents.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "unlink_agents.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agents/_bulk/unlink" + assert called_kwargs["method"] == "POST" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["criteria", "items", "not_items"]) diff --git a/tests/unit/plugins/modules/test_update_agent_configuration.py b/tests/unit/plugins/modules/test_update_agent_configuration.py new file mode 100644 index 0000000..c4cc164 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_agent_configuration.py @@ -0,0 +1,46 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_agent_configuration import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_agent_configuration.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_agent_configuration(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_id": 12345, + "auto_unlink": {"enabled": True, "expiration": 180}, + "software_update": True, + } + + expected_payload = {"auto_unlink": {"enabled": True, "expiration": 180}, "software_update": True} + + with patch( + BASE_MODULE_PATH + "update_agent_configuration.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_agent_configuration.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/12345/agents/config" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["software_update", "auto_unlink"]) diff --git a/tests/unit/plugins/modules/test_update_agent_exclusion.py b/tests/unit/plugins/modules/test_update_agent_exclusion.py new file mode 100644 index 0000000..65a3f57 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_agent_exclusion.py @@ -0,0 +1,63 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_agent_exclusion import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_agent_exclusion.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_agent_exclusion(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "exclusion_id": 124234, + "name": "Updated exclusion name", + "description": "Updated description", + "schedule": { + "enabled": True, + "starttime": "2024-06-01 00:00:00", + "endtime": "2024-07-07 23:59:59", + "timezone": "US/Pacific", + "rrules": {"freq": "ONETIME", "interval": 1, "byweekday": "SU", "bymonthday": 1}, + }, + } + + expected_payload = { + "name": "Updated exclusion name", + "description": "Updated description", + "schedule": { + "enabled": True, + "starttime": "2024-06-01 00:00:00", + "endtime": "2024-07-07 23:59:59", + "timezone": "US/Pacific", + "rrules": {"freq": "ONETIME", "interval": 1, "byweekday": "SU", "bymonthday": 1}, + }, + } + + with patch( + BASE_MODULE_PATH + "update_agent_exclusion.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_agent_exclusion.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agents/exclusions/124234" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name", "description", "schedule"]) diff --git a/tests/unit/plugins/modules/test_update_agent_group_name.py b/tests/unit/plugins/modules/test_update_agent_group_name.py new file mode 100644 index 0000000..9d15ec5 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_agent_group_name.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_agent_group_name import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_agent_group_name.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_agent_group_name(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 123456, + "name": "new_name", + } + + expected_payload = {"name": "new_name"} + + with patch( + BASE_MODULE_PATH + "update_agent_group_name.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_agent_group_name.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/null/agent-groups/123456" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) diff --git a/tests/unit/plugins/modules/test_update_an_exclusion.py b/tests/unit/plugins/modules/test_update_an_exclusion.py new file mode 100644 index 0000000..3f8f90a --- /dev/null +++ b/tests/unit/plugins/modules/test_update_an_exclusion.py @@ -0,0 +1,69 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_an_exclusion import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_an_exclusion.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_an_exclusion(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "exclusion_id": 1, + "name": "name", + "description": "i am the exclusion", + "members": "192.168.1.10", + "schedule": { + "enabled": True, + "starttime": "2023-04-01 09:00:00", + "endtime": "2023-04-01 17:00:00", + "timezone": "America/New_York", + "rrules": {"freq": "WEEKLY", "interval": 1, "byweekday": "MO,TU,WE,TH,FR"}, + }, + "network_id": "123456", + } + + expected_payload = { + "name": "name", + "description": "i am the exclusion", + "members": "192.168.1.10", + "schedule": { + "enabled": True, + "starttime": "2023-04-01 09:00:00", + "endtime": "2023-04-01 17:00:00", + "timezone": "America/New_York", + "rrules": {"freq": "WEEKLY", "interval": 1, "byweekday": "MO,TU,WE,TH,FR"}, + }, + "network_id": "123456", + } + + with patch( + BASE_MODULE_PATH + "update_an_exclusion.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_an_exclusion.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "exclusions/1" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["name", "members", "description", "schedule", "network_id"] + ) diff --git a/tests/unit/plugins/modules/test_update_attribute.py b/tests/unit/plugins/modules/test_update_attribute.py new file mode 100644 index 0000000..3af0fcc --- /dev/null +++ b/tests/unit/plugins/modules/test_update_attribute.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_attribute import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_attribute.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_attribute(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "attribute_id": "123", + "description": "Updated description of the attribute", + } + + expected_payload = {"description": "Updated description of the attribute"} + + with patch( + BASE_MODULE_PATH + "update_attribute.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_attribute.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "api/v3/assets/attributes/123" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["description"]) diff --git a/tests/unit/plugins/modules/test_update_group.py b/tests/unit/plugins/modules/test_update_group.py new file mode 100644 index 0000000..6e06288 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_group.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 1233, + "name": "New Group Name", + } + + expected_payload = {"name": "New Group Name"} + + with patch( + BASE_MODULE_PATH + "update_group.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_group.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "groups/1233" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) diff --git a/tests/unit/plugins/modules/test_update_managed_credential.py b/tests/unit/plugins/modules/test_update_managed_credential.py new file mode 100644 index 0000000..34643d1 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_managed_credential.py @@ -0,0 +1,57 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_managed_credential import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_managed_credential.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_managed_credential(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "credential_uuid": "123456", + "name": "name", + "description": "description", + "settings": {"domain": "domain", "username": "username", "auth_method": "Password", "password": "password"}, + "permissions": [{"grantee_uuid": "grantee_uuid", "type": "user", "permissions": 64, "name": "name"}], + "ad_hoc": True, + } + + expected_payload = { + "name": "name", + "description": "description", + "settings": {"domain": "domain", "username": "username", "auth_method": "Password", "password": "password"}, + "permissions": [{"grantee_uuid": "grantee_uuid", "type": "user", "permissions": 64, "name": "name"}], + "ad_hoc": True, + } + + with patch( + BASE_MODULE_PATH + "update_managed_credential.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_managed_credential.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "credentials/123456" + assert called_kwargs["method"] == "POST" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with( + mock_module.return_value, ["name", "description", "type", "settings", "permissions", "ad_hoc"] + ) diff --git a/tests/unit/plugins/modules/test_update_network.py b/tests/unit/plugins/modules/test_update_network.py new file mode 100644 index 0000000..fe63899 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_network.py @@ -0,0 +1,47 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_network import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_network.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_network(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "network_id": "123456", + "name": "new_name", + "description": "i am the description", + "assets_ttl_days": 60, + } + + expected_payload = {"name": "new_name", "description": "i am the description", "assets_ttl_days": 60} + + with patch( + BASE_MODULE_PATH + "update_network.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_network.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "networks/123456" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name", "description", "assets_ttl_days"]) diff --git a/tests/unit/plugins/modules/test_update_scan.py b/tests/unit/plugins/modules/test_update_scan.py new file mode 100644 index 0000000..dd87c65 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_scan.py @@ -0,0 +1,49 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_scan import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_scan.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_scan(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": 123456789, + "uuid": "123456789", + "settings": {"name": "name_scan_creation", "agent_group_id": "agent_group_id_created"}, + } + + expected_payload = { + "uuid": "123456789", + "settings": {"name": "name_scan_creation", "agent_group_id": "agent_group_id_created"}, + } + + with patch( + BASE_MODULE_PATH + "update_scan.build_complex_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_scan.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scans/123456789" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value.params) diff --git a/tests/unit/plugins/modules/test_update_scan_routes.py b/tests/unit/plugins/modules/test_update_scan_routes.py new file mode 100644 index 0000000..9d2b415 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_scan_routes.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_scan_routes import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_scan_routes.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_scan_routes(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 12345, + "routes": ["example.com", "192.0.2.0/24", "host.domain.com"], + } + + expected_payload = {"routes": ["example.com", "192.0.2.0/24", "host.domain.com"]} + + with patch( + BASE_MODULE_PATH + "update_scan_routes.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_scan_routes.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanner-groups/12345/routes" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["routes"]) diff --git a/tests/unit/plugins/modules/test_update_scan_status.py b/tests/unit/plugins/modules/test_update_scan_status.py new file mode 100644 index 0000000..0d37c32 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_scan_status.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_scan_status import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_scan_status.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_scan_status(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scan_id": "123456", + "read": True, + } + + expected_payload = {"read": True} + + with patch( + BASE_MODULE_PATH + "update_scan_status.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_scan_status.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scans/123456/status" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["read"]) diff --git a/tests/unit/plugins/modules/test_update_scanner.py b/tests/unit/plugins/modules/test_update_scanner.py new file mode 100644 index 0000000..93511a6 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_scanner.py @@ -0,0 +1,56 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_scanner import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_scanner.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_scanner(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "scanner_id": 12345, + "name": "New Scanner Name", + "force_plugin_update": 1, + } + + expected_payload = {"name": "New Scanner Name", "force_plugin_update": 1} + + with patch( + BASE_MODULE_PATH + "update_scanner.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_scanner.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanners/12345" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with( + mock_module.return_value, + [ + "name", + "force_plugin_update", + "force_ui_update", + "finish_update", + "registration_code", + "aws_update_interval", + ], + ) diff --git a/tests/unit/plugins/modules/test_update_scanner_group.py b/tests/unit/plugins/modules/test_update_scanner_group.py new file mode 100644 index 0000000..43f6a87 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_scanner_group.py @@ -0,0 +1,45 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_scanner_group import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_scanner_group.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_scanner_group(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "group_id": 12345, + "name": "New Group Name", + } + + expected_payload = {"name": "New Group Name"} + + with patch( + BASE_MODULE_PATH + "update_scanner_group.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_scanner_group.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "scanner-groups/12345" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name"]) diff --git a/tests/unit/plugins/modules/test_update_tag_category.py b/tests/unit/plugins/modules/test_update_tag_category.py new file mode 100644 index 0000000..bf44f3f --- /dev/null +++ b/tests/unit/plugins/modules/test_update_tag_category.py @@ -0,0 +1,46 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_tag_category import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_tag_category.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_tag_category(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "category_uuid": "12345", + "name": "New Category Name", + "description": "New Category Description", + } + + expected_payload = {"name": "New Category Name", "description": "New Category Description"} + + with patch( + BASE_MODULE_PATH + "update_tag_category.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_tag_category.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "tags/categories/12345" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["name", "description"]) diff --git a/tests/unit/plugins/modules/test_update_tag_value.py b/tests/unit/plugins/modules/test_update_tag_value.py new file mode 100644 index 0000000..7435cb5 --- /dev/null +++ b/tests/unit/plugins/modules/test_update_tag_value.py @@ -0,0 +1,46 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.update_tag_value import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "update_tag_value.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_update_tag_value(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "value_uuid": "12345", + "value": "New Value", + "description": "New Value Description", + } + + expected_payload = {"value": "New Value", "description": "New Value Description"} + + with patch( + BASE_MODULE_PATH + "update_tag_value.build_payload", return_value=expected_payload + ) as mock_build_payload, patch(BASE_MODULE_PATH + "update_tag_value.run_module") as mock_run_module: + main() + + mock_run_module.assert_called_once() + called_args, called_kwargs = mock_run_module.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "tags/values/12345" + assert called_kwargs["method"] == "PUT" + assert called_kwargs["data"] == expected_payload + + mock_build_payload.assert_called_once_with(mock_module.return_value, ["value", "description"]) diff --git a/tests/unit/plugins/modules/test_upload_file.py b/tests/unit/plugins/modules/test_upload_file.py new file mode 100644 index 0000000..ff0ed68 --- /dev/null +++ b/tests/unit/plugins/modules/test_upload_file.py @@ -0,0 +1,40 @@ +# (c) 2024, Fernando Mendieta (fernandomendietaovejero@gmail.com) +# GNU General Public License v3.0+ (see COPYING o https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +__metaclass__ = type + +from unittest.mock import patch + +import pytest +from ansible_collections.valkiriaaquatica.tenable.plugins.modules.upload_file import main +from ansible_collections.valkiriaaquatica.tenable.tests.unit.constants import BASE_MODULE_PATH + + +@pytest.fixture +def mock_module(): + with patch(BASE_MODULE_PATH + "upload_file.AnsibleModule", autospec=True) as mock: + yield mock + + +def test_upload_file(mock_module): + mock_module.return_value.params = { + "access_key": "fake_access_key", + "secret_key": "fake_secret_key", + "file_path": "/tmp/test_file.txt", + "no_enc": 1, + } + + with patch(BASE_MODULE_PATH + "upload_file.run_module_with_file") as mock_run_module_with_file: + main() + + mock_run_module_with_file.assert_called_once() + called_args, called_kwargs = mock_run_module_with_file.call_args + assert called_args[0] == mock_module.return_value + assert called_args[1] == "file/upload" + assert called_args[2] == "/tmp/test_file.txt" + assert "no_enc" in mock_module.return_value.params + assert mock_module.return_value.params["no_enc"] == 1 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..dc42eaa --- /dev/null +++ b/tox.ini @@ -0,0 +1,114 @@ +[tox] +# do not build the distribution package +skipsdist = True +# default list of test environments +envlist = clean,ansible{2.12,2.13}-py{38,39,310},linters +# Tox4 supports labels which allow us to group the environments +# rather than dumping all commands into a single environment +labels = + # grouping for formatting environments + format = flynt, black, isort + # grouping for linting environments + lint = complexity-report, ansible-lint, black-lint, isort-lint, flake8-lint, flynt-lint + # grouping for unit test environments + units = ansible{2.12,2.13}-py{38,39,310} + +[common] +# common directories for formatting, is the root of the collection +format_dirs = {toxinidir}/plugins {toxinidir}/tests + +[testenv] +description = Run the test-suite and generate an HTML coverage report +deps = + pytest + pytest-cov + ansible2.12: ansible-core>2.12,<2.13 + ansible2.13: ansible-core>2.13,<2.14 + !ansible2.12-!ansible2.13: ansible-core + pytest-ansible + -rtest-requirements.txt +commands = pytest --cov-report html --cov plugins/module_utils --cov plugins/modules plugins {posargs:tests/} + +[testenv:clean] +deps = coverage +skip_install = true +commands = coverage erase + +[testenv:complexity-report] +description = Generate a HTML complexity report in the complexity directory +deps = + # See: https://github.com/lordmauve/flake8-html/issues/30 + flake8>=3.3.0,<5.0.0 + flake8-html +commands = -flake8 --select C90 --max-complexity 10 --format=html --htmldir={posargs:complexity} plugins + +[testenv:ansible-lint] +deps = + ansible-lint +commands = + ansible-lint {toxinidir}/plugins + +[testenv:black] +depends = + flynt, isort +deps = + black >=23.0, <24.0 +commands = + black {[common]format_dirs} + +[testenv:black-lint] +# Check code formatting with black +deps = + {[testenv:black]deps} +commands = + black -v --check --diff {[common]format_dirs} + +[testenv:isort] +# Sort imports with isort +deps = + isort +commands = + isort {[common]format_dirs} + +[testenv:isort-lint] +deps = + {[testenv:isort]deps} +commands = + isort --check-only --diff {[common]format_dirs} + +[testenv:flake8-lint] +deps = + flake8 +commands = + flake8 {posargs} {[common]format_dirs} + +[testenv:flynt] +deps = + flynt +commands = + flynt {[common]format_dirs} + +[testenv:flynt-lint] +deps = + flynt +commands = + flynt --dry-run --fail-on-change {[common]format_dirs} + +[testenv:linters] +# Run linters black, isort, and flake8 +deps = + {[testenv:black]deps} + {[testenv:isort]deps} + flake8 +commands = + black -v --check {toxinidir}/plugins {toxinidir}/tests + isort --check-only --diff {toxinidir}/plugins {toxinidir}/tests + flake8 {posargs} {toxinidir}/plugins {toxinidir}/tests + +[flake8] +# Configuration for flake8 +# E123, E125 skipped as they are invalid PEP-8. +show-source = True +ignore = E123,E125,E203,E402,E501,E741,F401,F811,F841,W503 +max-line-length = 160 +builtins = _