diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg.opnsense-25.7.8.info/GALAXY.yml b/ansible/playbooks/collections/ansible_collections/oxlorg.opnsense-25.7.8.info/GALAXY.yml new file mode 100644 index 0000000..5b7af4b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg.opnsense-25.7.8.info/GALAXY.yml @@ -0,0 +1,8 @@ +download_url: https://galaxy.ansible.com/api/v3/plugin/ansible/content/published/collections/artifacts/oxlorg-opnsense-25.7.8.tar.gz +format_version: 1.0.0 +name: opnsense +namespace: oxlorg +server: https://galaxy.ansible.com/api/ +signatures: [] +version: 25.7.8 +version_url: /api/v3/plugin/ansible/content/published/collections/index/oxlorg/opnsense/versions/25.7.8/ diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/CONTRIBUTING.md b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/CONTRIBUTING.md new file mode 100644 index 0000000..d072dd1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contribute + +Thank you for considering to contribute to this project! + +## What to contribute + +* report errors as [issues](https://github.com/O-X-L/ansible-opnsense/issues) +* test unstable modules and [report if they work as expected](https://github.com/O-X-L/ansible-opnsense/discussions/new?category=general) +* add [ansible-based tests](https://github.com/O-X-L/ansible-opnsense/blob/latest/tests) for some error-case(s) you have encountered +* extend or correct the [documentation](https://github.com/O-X-L/ansible-opnsense/blob/latest/docs) +* add missing inline documentation [as standardized](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html#documentation-block) + * should be placed in `/plugins/module_utils/inline_docs/.py` and then imported in the module +* contribute code fixes or optimizations +* implement additional API endpoints/modules + +## Module development + +See: [Documentation - Development](https://ansible-opnsense.oxl.app/usage/4_develop.html) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/FILES.json b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/FILES.json new file mode 100644 index 0000000..f24d7a5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/FILES.json @@ -0,0 +1,3645 @@ +{ + "files": [ + { + "name": ".", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "tests/haproxy_general_stats.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da0a104a3ad061fea2deddbe014c9812353fa9630c0b0c5451645a37d9c962bb", + "format": 1 + }, + { + "name": "tests/frr_ospf_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5773f91c884e6dc75ebd47dc23baa427588afe00a66f4817e543d4f8222b988e", + "format": 1 + }, + { + "name": "tests/frr_bgp_peer_group.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "27e0cf215cb793291b50106bbece2d5d812f01bc670db9ebfba0b1a34b4ea731", + "format": 1 + }, + { + "name": "tests/postfix_recipientbcc.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dbed5cf38465e47e15e99a34dd9d3a12340fe3398c1f29f66d60e209b1df16fc", + "format": 1 + }, + { + "name": "tests/system.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9ec4fcfffebe395b0eee904b4e46e5b1114c20103476e620ac83638ee9ea7123", + "format": 1 + }, + { + "name": "tests/package.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3e79f816b4510862277b0dc896fc585c25b53fdaa80795d205a9c912c1c92ee4", + "format": 1 + }, + { + "name": "tests/snapshot.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "534e8d5006212681b1e4daf72b29f91e7edb634f9a29dcb177e925a8e1b1afbd", + "format": 1 + }, + { + "name": "tests/bind_blocklist.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "32c7bef61f320f0e803d5e8b09fafd7f7b5a36ce6f1859cd00dad9874facdede", + "format": 1 + }, + { + "name": "tests/openvpn_server.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b97ad26a074899c903c1f329dda277625ca48b2ce223a7fcb572122167d887ed", + "format": 1 + }, + { + "name": "tests/neighbor.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "76c8013e56c68450c4335b642d5c8d664aa3758431d95d3b43faf26dcbd17ee8", + "format": 1 + }, + { + "name": "tests/dnsmasq_boot.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6a654d357059613248f5023cfb0c6a1d876ff1456164dcaf2a55a91ebad04052", + "format": 1 + }, + { + "name": "tests/bind_acl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "83a4a850962f94822799ed6d35787573da66aaf533e6fb386534b48fe189281c", + "format": 1 + }, + { + "name": "tests/interface_vip.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "17ebdd5c7543873c40d42498c137fdee34495f2cd7e5de6812e8d0af9ea1f1f0", + "format": 1 + }, + { + "name": "tests/ipsec_manual_spd.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2e2d2cb6236eb2c5f7e695315f5c10dd0c82f2d5ce12899eaaaf46f9b5d5a594", + "format": 1 + }, + { + "name": "tests/haproxy_general_peers.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "88063cffc048f99dd69c66c6f7c9b1867bf6cddd11811ee061aafdb31f133255", + "format": 1 + }, + { + "name": "tests/acme_action.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7f011b3e55c7aae246520d894196c757292e18d55b9d12081362a1fed26364e3", + "format": 1 + }, + { + "name": "tests/interface_gif.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4f5e02615fb10d251da6554fc365f23f699a4558e258e52a4a09316d2c8beee1", + "format": 1 + }, + { + "name": "tests/webproxy_acl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f07987c100b65a534acb96460cf1181a2b0884b5e3e9c4415264f3698253e39b", + "format": 1 + }, + { + "name": "tests/nat_one_to_one.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cf39f066874eb24c2c52a28fda811a550effe73dac21c26c108fd9edd57f2f9", + "format": 1 + }, + { + "name": "tests/frr_bfd_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b3338bab42b2c56c8c61bb2e8ee14a311a8b2777b9530fb9c0aec64c2fff6d4c", + "format": 1 + }, + { + "name": "tests/dnsmasq_option.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "619c9c1454578d73101abd00ddc2a80be3086dcb4a9b4dc5bd42177a57b2986c", + "format": 1 + }, + { + "name": "tests/nginx_upstream_server.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0742e14d2dec3df862f112c909362b8418e8b1bb5ed42c3c61965cd586f5e09d", + "format": 1 + }, + { + "name": "tests/alias.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e636d5c2c3c12b2e199247fc0394459c51a6d838f0aa75767c924d270a07b2f4", + "format": 1 + }, + { + "name": "tests/dnsmasq_domain.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "786614ed24ea4929eb183308c396f9c1cc55da084382f64ea8b71d54fd26d2b2", + "format": 1 + }, + { + "name": "tests/haproxy_acl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "22591110a67b84b44da4f3a7a15d3a181aee5693ba7bbdcca9b89267d01b3cbe", + "format": 1 + }, + { + "name": "tests/frr_bgp_redistribution.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "37ac49e9830b21a331e4d9baa2ff62a426e00f2db9b752a5437229d07529284d", + "format": 1 + }, + { + "name": "tests/ids_ruleset.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d8a8f5e209b34bf2f234ae641ff89a2321abb39e9c5e830b33de08e150c134cd", + "format": 1 + }, + { + "name": "tests/unbound_dnsbl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "03efe5dbc658add4e50bde987465231868bd5283f6f4c484432213e21553ea98", + "format": 1 + }, + { + "name": "tests/frr_ospf_network.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "af1f8d5ad9f4f1c60fe3387804d0664c2c949ea502dcc7198faeed703ac2128f", + "format": 1 + }, + { + "name": "tests/wazuh_agent.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60d336b3bad75c0ee7e8a6b322e1746554a9326d20aead65babd83507b2a7b74", + "format": 1 + }, + { + "name": "tests/acme_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c31ca440e607c84fb8c340f225c8f19f4769d6536e570dbe7b73a1240b709daf", + "format": 1 + }, + { + "name": "tests/bind_record_multi.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aa414a4e68cdba7d981cb170a95331f8e01cff7838b544675f662c75cbd2d563", + "format": 1 + }, + { + "name": "tests/savepoint.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "65bb3af6ea4d0de25af1e1d591ea637b5f8741d26e8bb62976f7ff46771de198", + "format": 1 + }, + { + "name": "tests/shaper_pipe.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "63a260c4dec911d8aedac500f042ec9ce77cb4dd9000aff5336fc28a7b214166", + "format": 1 + }, + { + "name": "tests/unbound_acl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "453246537ea7e739edcb7e1ce9fd174d2969dc024c6095210138204cbf20351e", + "format": 1 + }, + { + "name": "tests/webproxy_pac_match.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "55bde30fc4ee8cb854a75a84886ad5aa5884a5fb64232a898f7864ac47e11b1b", + "format": 1 + }, + { + "name": "tests/ipsec_child.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3d6563971e183176a9c6f661b7fb6e20431edf1b8d1b462e532cfc2c5cb9af26", + "format": 1 + }, + { + "name": "tests/cron.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3eb079812063e05b42384f25181a07dc6dc59fe5e674fae07ed0dbb278d6021b", + "format": 1 + }, + { + "name": "tests/postfix_sendercanonical.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9d0e5470d188d8f93b310fa1580e4bcbfd0ade11b668a32e8804395ab29b52ce", + "format": 1 + }, + { + "name": "tests/frr_ospf_prefix_list.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cab7b41730d27ce55acce9b510c7696ee70cd53718eb2b0f49ab65193484423d", + "format": 1 + }, + { + "name": "tests/haproxy_user.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cb7c033ca289d4182250f01aed76cb7cd04ce1bb8942ddd3a96ef5d3db1e7101", + "format": 1 + }, + { + "name": "tests/unbound_forward.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b460912c553b5e4a6677f0576765b7a0d12b36bc50d9ec84692a744251a0335d", + "format": 1 + }, + { + "name": "tests/openvpn_status.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b14bac0a387701222312258dceb613b0ef5c1ea7e341ce14bb078d50889aebd", + "format": 1 + }, + { + "name": "tests/postfix_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cd5683ad6f537f5346f231d1db8e22d0b24611d4e0536334c0ebac39455e2dd9", + "format": 1 + }, + { + "name": "tests/interface_vxlan.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cc70a85df008f82ece52eb8ccec4dfdd11ec87060af7c141956555519ede9387", + "format": 1 + }, + { + "name": "tests/openvpn_static_key.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a5401d3828be6f3076cec509eabbad4c13efb844375d75f140997ce2bf528a92", + "format": 1 + }, + { + "name": "tests/frr_bgp_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6e88a25d43ce991083fceaa6eed276a6e458b7657f38f79838e15f78e8bd3a29", + "format": 1 + }, + { + "name": "tests/frr_bgp_route_map.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c06268dd50eb2d2ff2633a1840e04e822a4a6bb1a10dcd730bd1ef1a9ed4f192", + "format": 1 + }, + { + "name": "tests/bind_domain.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ca89ec6962f5a59f5a5e6c2f30283456c4afc15b965a860bda800adb65df459d", + "format": 1 + }, + { + "name": "tests/bind_record.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "23055b406b50857072a3acee22db3f194bc999ab91b98796ca78e7e9069bd5e3", + "format": 1 + }, + { + "name": "tests/bind_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bcf330c8f365e82962c77a1a7108a9651a7fe60fe000ef6b31491b780f6b0f13", + "format": 1 + }, + { + "name": "tests/frr_rip.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a4806065a03df2459656c316d2b933d21f852e0dca3b473f9241841e1c070894", + "format": 1 + }, + { + "name": "tests/alias_multi.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f530a49809fd3ce24b332792dc83a0427da40ef9127de0c1a5cfe4985e8d69bf", + "format": 1 + }, + { + "name": "tests/rule_interface_group.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "38c495efaab74c77f45eaf90107b4ed04d0c453a4be67bad9d1bd766214599dd", + "format": 1 + }, + { + "name": "tests/rule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e30314d4f2ddfe310d3832215850b916e3ba5c918519b9560c3a5446037ccdb7", + "format": 1 + }, + { + "name": "tests/README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c9c4f79a67cef9212926c1d70bae172f7af0df8ffc18fe2270c523b37fd5295b", + "format": 1 + }, + { + "name": "tests/frr_ospf_interface.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3aad202cc04f28a10b39c05d110312c5bca839e20bde8d2209cdcf7e2bd584fa", + "format": 1 + }, + { + "name": "tests/syslog.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "41bc5c98fd33cebb3fdb971800771df36e641bf1417d64df18b911c18fbb30cb", + "format": 1 + }, + { + "name": "tests/frr_ospf_route_map.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d80a7c7b239493112e14ab8b0a82d9187f9fedb19effae56bd7a81949a1350e8", + "format": 1 + }, + { + "name": "tests/haproxy_general_cache.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "050cfa20829a4a402534de9ee619ed5eafcb858c6eccedd50bff8a5fd56e96c2", + "format": 1 + }, + { + "name": "tests/webproxy_pac_proxy.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0125953e6d4e3fceb4bad07d686897f7e26b56a7dcc7533430faa52daa7663a6", + "format": 1 + }, + { + "name": "tests/ids_rule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a3dea3cfc93c98e05a69555bc1b27b04347b591ffd485bbad01693f1f10fbe01", + "format": 1 + }, + { + "name": "tests/ids_policy_rule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e46d2d85312fb0f0126dc3223d3aebeb8f2ce96ab4797e377b828bcc8ce1e26c", + "format": 1 + }, + { + "name": "tests/haproxy_general_defaults.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3ece4db6a096a36e17f844d135d54121d79bc967740f648ff894cec67c847914", + "format": 1 + }, + { + "name": "tests/shaper_queue.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e512502f1801e6a7d7e82e819b685331b7809b694928c9ea4e9580274fd98ad5", + "format": 1 + }, + { + "name": "tests/raw.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0c20d7498da792f1c77ac4b5c4748501849f60422e5cb1d6e00112219f6698cb", + "format": 1 + }, + { + "name": "tests/dnsmasq_host.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e9c194f7402d7cc4f33eb49d7a6f9964f4b62300ba904c0f59f2e6363df3439e", + "format": 1 + }, + { + "name": "tests/wireguard_peer.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0d6ae41c8b4cf1ec03eb7b331c3681c82377de92d6d9e4977fe8f75f23c48f74", + "format": 1 + }, + { + "name": "tests/frr_ospf3_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f5076c6e706bb3e0a9bb6c83a0f65f9cdd91d0dedc0cb08fd6f9e724f3e3c3de", + "format": 1 + }, + { + "name": "tests/postfix_headercheck.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7dd2fb16bddac62f226c971c3e88d3599fd50ad879edc37ab467fbe3bf4a9c69", + "format": 1 + }, + { + "name": "tests/acme_validation.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e5acd5f33fa8844a22ea2102b99aa69116136e0146798fa43cc8ef519cd04107", + "format": 1 + }, + { + "name": "tests/dnsmasq_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2d6f103f77d6e5e1c0063851a89f25be70da07c0ee1cc66f9fcbdc1abeceff8e", + "format": 1 + }, + { + "name": "tests/ids_policy.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b8a1ba94f77c1cf11cdb24ce764d89a2772c386b210cc72c7a62d0d1d972c382", + "format": 1 + }, + { + "name": "tests/hasync_service.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "06da25ada80d968c767c0b7e0cb851bdae388022c3a1d51877d598ad05d25f03", + "format": 1 + }, + { + "name": "tests/ipsec_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1cff400a2df543d3fd587aaf5f67d798c4d6ad3fdbffcddb601380e5327e175a", + "format": 1 + }, + { + "name": "tests/unbound_dot.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "79b2ae5edbb9ab508f18a1a49586c907eff276bd0ab503d620e7bcc380ccfdf0", + "format": 1 + }, + { + "name": "tests/postfix_domain.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c8ba6e18058bc1ca7293e6abdbbbfeb4b0618e0bf9cec71ceb5f77f4dd8643ad", + "format": 1 + }, + { + "name": "tests/webproxy_cache.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e52b724eb5498a7100fd12c6bdf12d59f3ca8d3805117c378db3f1fb1a0aa7e2", + "format": 1 + }, + { + "name": "tests/rule_multi.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ded91e05a5326e7532f8a7e2c2f52e8e02c5d24df1b555605280c36fcfc1c6d", + "format": 1 + }, + { + "name": "tests/ipsec_pool.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "14cc753337d1055e033f72a7fff9b7c7e98df0427718b8d0ca260ec73b94bb64", + "format": 1 + }, + { + "name": "tests/unbound_host.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5c7a54d69de8067a4d875ea0001f151f535cbe8861e4fa7e4450f5c9b3e1afc4", + "format": 1 + }, + { + "name": "tests/frr_bgp_as_path.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "85937f653a843eb1ab5d2f9c789f465ff9f628c43fe00da646b69050d27a68fb", + "format": 1 + }, + { + "name": "tests/dhcp_subnet.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2441ed64758a2f0773d4bd3b0529c16bb3de6e680e1f48faa7435650ed0dd3b7", + "format": 1 + }, + { + "name": "tests/frr_ospf_redistribution.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "71f91c6d46bca8a0da3661328a643ffe672f8775dc0d5fb17cbdffdce27839e3", + "format": 1 + }, + { + "name": "tests/frr_bfd_neighbor.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08be4bf074736f57358842ca7d14968c67a0519acdacd3ed87d061782463071d", + "format": 1 + }, + { + "name": "tests/monit_test.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c0b923a18d4fd7d2eb04a1e5bc06b993a42242cfbe02aa68b204061b593d69f8", + "format": 1 + }, + { + "name": "tests/postfix_recipient.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a1509048f097e3fb883385e538770546d72387c1c8f00a50d832c26faad1fc1", + "format": 1 + }, + { + "name": "tests/dhcrelay_relay.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "35b3bbdde0d8696bf51b3b9e62d53a00bf239d658ba8210c317c031ead795daa", + "format": 1 + }, + { + "name": "tests/route.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d1abd14a31971ae0a6522a26a772d09f291efb2d6bb645b34ca720facd71c126", + "format": 1 + }, + { + "name": "tests/postfix_senderbcc.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4e28d52fc586d83a2f6da19c258966f4461f032cf821bf5218311cc599efa91b", + "format": 1 + }, + { + "name": "tests/interface_lagg.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cca7d58b3b3a36bd5bdc680a6a4cb1f216179f566ac952d26ddb04fcbdbc8cfa", + "format": 1 + }, + { + "name": "tests/wireguard_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1253081bb09f524d9d02d15545bdd727f6575779cdf38e305b6950284c115d46", + "format": 1 + }, + { + "name": "tests/ids_action.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2307fc18b8629bf12f4322dec70d731c68418a28b054705c0e343ff8a9e3a871", + "format": 1 + }, + { + "name": "tests/interface_gre.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "051052909a10db64e1de037892cc80c08ac39e8a6de35356f59759c2e8b84ff8", + "format": 1 + }, + { + "name": "tests/nginx_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73a442f6f3c7ff860ffa0f88c7bd5842a297225652cc2579b48da82e3c5569ae", + "format": 1 + }, + { + "name": "tests/frr_bgp_neighbor.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "28757a44a2de78fe3f738724d56bbbf255d173ef4a0710eda5318762f5542682", + "format": 1 + }, + { + "name": "tests/rule_purge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1752695a6743bbf9d550d44e8202ee15e4ad6b27a4f706e6c17357f048115c5b", + "format": 1 + }, + { + "name": "tests/haproxy_fcgi.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "252466347cf2bde2c13fb08851812b7cc41cd793ebaa212055193fab12c430cd", + "format": 1 + }, + { + "name": "tests/webproxy_forward.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a2a9edae75095214c35f613c69f261af73e5f76470e95f6f5b024ce959d0fe49", + "format": 1 + }, + { + "name": "tests/hasync_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d8f244e2b9e1d033ca2628265900f0739818cd6e526b21e4968ed7779c58923f", + "format": 1 + }, + { + "name": "tests/alias_purge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3bb7ad67b188937c8d9bfb92560add1132779bf6aed540a2f4404a65d351ae5c", + "format": 1 + }, + { + "name": "tests/frr_ospf3_interface.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "adb7d3a3e2ed917c2d52789845812ee5ab4622b5e22998dd89a1024a9b88a7be", + "format": 1 + }, + { + "name": "tests/privilege.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a536a1917fe79078b5a85c91594fa1a174077ab64c52cb254dd5252ecb5470c5", + "format": 1 + }, + { + "name": "tests/postfix_sender.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "55e6e11816ca0cec0aa4ff88734b25f77fce728e7f8d7702c5a50f6bf4cbc2ad", + "format": 1 + }, + { + "name": "tests/monit_service.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9bdbf065e82cad4671b6c7306d352d6e45ec8d3ef8d2f24ecf4f3a305626766e", + "format": 1 + }, + { + "name": "tests/webproxy_icap.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f3f5877be7dede65092a70503f76cb83fad8f0373dcf090bd93f66aa13c8f4b8", + "format": 1 + }, + { + "name": "tests/haproxy_general_settings.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f01565041f9552cc63ece53a65f7770f04fb84e6d358c75ab64601f30c63ae99", + "format": 1 + }, + { + "name": "tests/haproxy_group.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b97e687cdaed65d85049a11f846a8a34fb1f244b428706e135fa95da57777dd", + "format": 1 + }, + { + "name": "tests/service.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3475afee26ca22bacd8261c6a791e9a53f5c1d62d7279d1c3e2256f42a1c79d2", + "format": 1 + }, + { + "name": "tests/group.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "598e641d3e8cb1934f45fcfe3a120f773b0cbb2bee93e49ff148ba7f648a09d0", + "format": 1 + }, + { + "name": "tests/webproxy_parent.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "77a80e33342c757961097acaa472080494ecdbe70409fbaa935469cc3dd38308", + "format": 1 + }, + { + "name": "tests/user.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ed4b3fc748956473f9bfab9389b850005db16db304eab224467af125659507b4", + "format": 1 + }, + { + "name": "tests/unbound_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "718489e4505994c4d49877a2b3cf016d0c6aa461bf1335901287014c807630a0", + "format": 1 + }, + { + "name": "tests/acme_account.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d92bfcee95fd70e5c70b4e078ef455f29b4939911a7bc5f2bce29f0791509b61", + "format": 1 + }, + { + "name": "tests/haproxy_general_logging.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dcbcddb8392ba76fd56bac66278e96cd82bb39aac66a941ec2d9b7161c31e65b", + "format": 1 + }, + { + "name": "tests/wireguard_show.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3154cc3580235f75b7edc809562da52ff151595c632cbe41d446a6759b90b61f", + "format": 1 + }, + { + "name": "tests/1_dependencies.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eabd8619150a747bbb4e5971774fbc1e4e84480ff78708b9f9d2f2fe90062529", + "format": 1 + }, + { + "name": "tests/frr_ospf3_route_map.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "27dce13182ce9267c0a5eb8d61280079ff052ba58152e98fb464e1efd991302e", + "format": 1 + }, + { + "name": "tests/openvpn_client.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6ba63b63e02e1b00637585a55e08a6b372f419c5ffa984eb927675528a363d91", + "format": 1 + }, + { + "name": "tests/haproxy_general_tuning.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5830998e7801886d0a6b749aba20bae1c9e236431a2b88c9b3463a8dc6ce5d80", + "format": 1 + }, + { + "name": "tests/unbound_host_alias.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "18758eea3f8422f58e68b94f2206c954b5ff92501e848cd8c7501412597fb4b3", + "format": 1 + }, + { + "name": "tests/monit_alert.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5a71ffa4786eb6a5ab43edaedf9073d3b7ee66eb8f0b22c52bfbac197bf74e1b", + "format": 1 + }, + { + "name": "tests/haproxy_maintenance.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "384763acc9504d35f02345ae353cfcd5191e5ce98106578debcea3d6b309ddfe", + "format": 1 + }, + { + "name": "tests/dnsmasq_tag.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "458ac1e0d743e0f0034b9b36e09e0d8b432756ff58e49b431e91275814e93743", + "format": 1 + }, + { + "name": "tests/interface_loopback.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c5f5aec71db5a79b85eaf16493636e148aa72015c1ecc70bcaaf979931dee0f6", + "format": 1 + }, + { + "name": "tests/1_version.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3a10896c84f02534580baeebcb8bbdb59e6676c67d50b1d7a87eea57b6ac7293", + "format": 1 + }, + { + "name": "tests/interface_vlan.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4428339a221d5119acbbc3e5ba41329b19e25c2ca38fd71101c7e61d4c0e242f", + "format": 1 + }, + { + "name": "tests/frr_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e533a7e1874d84e0b275d9d61bbcc313711cbc525f6a586b6c124222ea061727", + "format": 1 + }, + { + "name": "tests/dhcp_controlagent.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bcdb6e661c81d2e4d079395948b6ab98becdfda57f82c02a0d9a1739b3d83241", + "format": 1 + }, + { + "name": "tests/acme_certificate.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "578c350c6e6ad7e85918c19a99f863ddad5f826f9b7689d888e18bc66d3551bc", + "format": 1 + }, + { + "name": "tests/openvpn_client_override.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8fe712ef2c7c049669c564e23f270957308190f3b89974a379bd17d525319f04", + "format": 1 + }, + { + "name": "tests/webproxy_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dc70199248aa344dbe025a11c418294a9394b8343bffa7bc1ff3d699e6cb560f", + "format": 1 + }, + { + "name": "tests/ipsec_vti.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9bcfff1dbcb1cad9709786e4c6578d990ebca46eb4a8ed51b9db97cbdce48bdc", + "format": 1 + }, + { + "name": "tests/frr_bgp_prefix_list.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b5ab911c2bcc4c942ae6d107893c33e2e5a5ad531aeccfa3a80bd04c52aa8db3", + "format": 1 + }, + { + "name": "tests/ipsec_psk.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fbf7684fcdd9efaf470cc81127f28a49c3e87eab927371523937ea1d84cbadfa", + "format": 1 + }, + { + "name": "tests/dnsmasq_range.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "300d0cc487f19a62a4839c13fc356e14b7f8e1ab758b424774da5d702133baae", + "format": 1 + }, + { + "name": "tests/1_cleanup.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a85194cbdaa493e799659635de43f194503d57fbabcfa628b4d2476d1b8e8f68", + "format": 1 + }, + { + "name": "tests/1_multi.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1bfdec078a624559d139d0b8e6f5aed3364a4a59d8a481dd2cd28404ccce86c9", + "format": 1 + }, + { + "name": "tests/_tmpl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3af53265fff023272d3d84a6b63330c7e7edd1fc7369849886a6cf0f77eb0c37", + "format": 1 + }, + { + "name": "tests/haproxy_lua.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "47e9e8f3deb3c7fd21508ce3e9be6500757f5b324a7fe51c44544a36ff270393", + "format": 1 + }, + { + "name": "tests/list.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8239f377a8767635409cc1657cee1574ff55da0b898fa6fa867d95688c51f360", + "format": 1 + }, + { + "name": "tests/ids_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "35f7ab2ee66c0370b4e06c83b4b573a4384694f32092f517443d99b249471ac6", + "format": 1 + }, + { + "name": "tests/ipsec_auth_local.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eb447dd3ca90b43a989ea881a201d2445bd5e96435091ff86a7e6d8a5e834b83", + "format": 1 + }, + { + "name": "tests/nat_source.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6f1b4f885a522812997eb91d6c7246ea84c71a96b0e9089f4d5468329d924f44", + "format": 1 + }, + { + "name": "tests/ipsec_connection.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2966a8704fa66d317b8fbb9963558eaf2918425daf23d0f8280897da1766ab60", + "format": 1 + }, + { + "name": "tests/frr_diagnostic.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e46e887db5f73080134b2c0ff9e04ac6ed5e82fc4619e63fe4c650c9446ef890", + "format": 1 + }, + { + "name": "tests/frr_ospf3_prefix_list.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3967fc635b93307141e0825622a7ec9856461f484a1c450f2428e04bb85fc9e3", + "format": 1 + }, + { + "name": "tests/reload.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3aa40a85a020c5325e7b1e55389b24ebbb2de8ee78d3753a42e8a2a857344c43", + "format": 1 + }, + { + "name": "tests/dhcrelay_destination.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aa743e1ab8d5e74125ea56469ccbed1642b3d37032960ccaeb8ed685e004ebbb", + "format": 1 + }, + { + "name": "tests/dhcp_reservation.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "56cabd016f2a8781dcefbd5db04d446b28d9c740dd55227e7438eabaa5edac9f", + "format": 1 + }, + { + "name": "tests/webproxy_auth.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0f70264cbfac346c6d34372ee565f4aee4bedcd043d141289bed281924a4fcb7", + "format": 1 + }, + { + "name": "tests/webproxy_remote_acl.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aebdb3b257ed2df9b3de6a62d766c9fc89e46883aa9c47e2095258ccc72fec2a", + "format": 1 + }, + { + "name": "tests/shaper_rule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1b81a5ceb50b90f581de8110f71aba70eee27405689854ac3da5ae3fdda8bf7e", + "format": 1 + }, + { + "name": "tests/webproxy_pac_rule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "defa32236e6e312975bf0a303bfa52dae76382a92b5e802cdcdf2d0170c0e655", + "format": 1 + }, + { + "name": "tests/webproxy_traffic.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "28d9bce5d8b7dfc7bd6af76db2bed0b802813cc6209f9c96edf0d958d5540e21", + "format": 1 + }, + { + "name": "tests/gateway.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f9c21f1a64ff9a47001f643082756559969e7a3e54f7d2c2a94a3bf7084d6da9", + "format": 1 + }, + { + "name": "tests/frr_ospf3_redistribution.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2419ea9cbca7872765a5170a46cc9234ece2189c5e59f23e3ff1f30ec9524671", + "format": 1 + }, + { + "name": "tests/ids_user_rule.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b634a41c8b056ed0c207abf1386265200d75a35240439f672ee3916b37f3dba3", + "format": 1 + }, + { + "name": "tests/dhcp_general.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0f1cab310de9e8ea2592b9b6c99027ba7c88fe8858015b9e78da64627f7a746a", + "format": 1 + }, + { + "name": "tests/ipsec_auth_remote.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f4d5a1f0ecd14c7f490b44c0d484d05b218e727b33474d6f46200fc11d834313", + "format": 1 + }, + { + "name": "tests/interface_bridge.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "df6fe1f11b741eb674e2ab6acb6ae6469d605044ee7866a32d5bf9208085654c", + "format": 1 + }, + { + "name": "tests/ipsec_cert.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "93e8c1a82330cc236ca9cb1ff5646824dd2b48a9d6095e5bf75afc39567928b3", + "format": 1 + }, + { + "name": "tests/haproxy_action.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6a2cbf3f241c1180281331323191e1e9f3fb86181a8188ec2e4edb32073754f4", + "format": 1 + }, + { + "name": "tests/frr_bgp_community_list.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd211b2f3287828abd547774db947f3aa1c2e34c7c2eb34ee5be4ae77deb00d5", + "format": 1 + }, + { + "name": "tests/postfix_address.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "638735a2d651be30459ab59b374f3d16331adebe4081f44c6ba84620a5b819c4", + "format": 1 + }, + { + "name": "tests/frr_ospf3_network.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "78c28318fbb3db225bc241cc456888997252b92c9f47e43f73b4f41c0c4f26b3", + "format": 1 + }, + { + "name": "tests/haproxy_cpu.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1f056c3920a0423ddb2f8b7cc23f86049ed77ca1c9693222924ad2fb1fbd794f", + "format": 1 + }, + { + "name": "tests/haproxy_errorfile.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "133b8ebc0a8c1e6cd20cf89c4ebc392a84d291d7b68742e81259e416249cfcb1", + "format": 1 + }, + { + "name": "tests/wireguard_server.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "976cad63ea325caaee3ccc25b2fef9b62c75843893a65dc3bfcfc2c26f47fc0a", + "format": 1 + }, + { + "name": "requirements.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2da62fd6ad0b42718531839e4c7fb3c690d511dfc174ac6a50171d5076016d16", + "format": 1 + }, + { + "name": "README.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "416df2221869059efe58e78ee9805ceb9b8ef37a008984bcfbfcc57eb225f7ba", + "format": 1 + }, + { + "name": "LICENSE.txt", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "25c70092f39f9b06ea0616e8e7ba83e4030243c60efc1c421e5af0458fdee965", + "format": 1 + }, + { + "name": "CONTRIBUTING.md", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8164acdc440c8982dc41a8c30429ffea9d281437c27b5d6efe69fbe7f3356ad2", + "format": 1 + }, + { + "name": "plugins", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/modules/postfix_recipientbcc.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3fb49f2d486c8292b781dad2fc6d991597ca961b7cda3ac301e5204438062a27", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_remote_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8791aea311670d2def820abd1921d3cbdd3233aacda9ecec9760cf614b235824", + "format": 1 + }, + { + "name": "plugins/modules/interface_loopback.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "271f7b751304a8d6d0bd0b4f8dbbca0d412cb92429ad7bc3354ade15f6cc4729", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0c7982ac6a314e6869d0faeed195c73be7488a0db4c77bda0fae3ca776fb8efe", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_auth.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8826c746c9f3f26c4ce6b81d4435c6a1a1e7c515235e7016480554ad3537d38e", + "format": 1 + }, + { + "name": "plugins/modules/ids_action.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "43407873ca259ad86d7107091319e57f55f0eb5d40c0351761672eac3e5895b7", + "format": 1 + }, + { + "name": "plugins/modules/dhcp_controlagent.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d090f5faf1f7c5c34279e746ff9e3fbad303e9c9ff1c6a751bdb96737b785cd7", + "format": 1 + }, + { + "name": "plugins/modules/bind_blocklist.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7a1a996ec0788edaf0899d9a8d61a5db22df2f2c91e6943ef6d4a36273d1da0f", + "format": 1 + }, + { + "name": "plugins/modules/interface_bridge.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8c5eb509042e1188192be4dddc79d59c75262fff14bd840bd9f52a8901c5094c", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_cert.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7048452049dcd5344ddddc8f788107eaa0ac50fc74ee7fb6ef6536359a26498e", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf3_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "215a48f9326deba94b5894d0996d22fb83bd39926547418d61860d3fad0118fa", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_fcgi.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7ff63d3886b5a8f81a286d834d396fda3b7617b9abd5bf37e2e6a01e2dbfbcdd", + "format": 1 + }, + { + "name": "plugins/modules/wireguard_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e845fabc6563a7d79936e1bacf76be9cbe1f0a370bdd6cb8c0be01131402980", + "format": 1 + }, + { + "name": "plugins/modules/acme_account.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b3771dc42f5021d71160e96b3af4314339fe74addcf59fb7135a7f86c4ea28b3", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_parent.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2cdc2a624294ffa279d3ae073fc89818d87c8486a34dec79112941fa1362f9c4", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_child.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d9ab00f030d749149e105718ff158f4c1b34f5733ced6921051d10796d7afc0e", + "format": 1 + }, + { + "name": "plugins/modules/shaper_pipe.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9f118101639b889e6a9e00bbd1c5edc49e25e7136cc6d8fcf6f5237a024bd9c1", + "format": 1 + }, + { + "name": "plugins/modules/privilege.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6e5c7c9784e6ec88ace7c224f774f111e247f14fd2f6a42202bcf50818616e7d", + "format": 1 + }, + { + "name": "plugins/modules/postfix_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f866c7776f2a0c0c864c63d62900b041647ed17e5ac70c0712d981b97b30e593", + "format": 1 + }, + { + "name": "plugins/modules/dhcp_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8407726c847542009408b8d498f2010ac1441588640b014a8ce4992e982ea925", + "format": 1 + }, + { + "name": "plugins/modules/syslog.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "901570e20316d6fc227c9baa7b8e96857bb7a1601812336c4c26a7572f2bccaa", + "format": 1 + }, + { + "name": "plugins/modules/dhcp_subnet.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fd1ea3800c9d9d2760a8d3541462c00eebde490ff7679eb27e9ba02c4d46ceb9", + "format": 1 + }, + { + "name": "plugins/modules/unbound_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "79dfc867b38e2d94c5d1e1cf9f267f0378195cf109627c9d363140eeb83f3146", + "format": 1 + }, + { + "name": "plugins/modules/ids_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e823b406919b9e154cefc7c7f7039c01fdbedb5aafe74bbe64cc48b074a30f28", + "format": 1 + }, + { + "name": "plugins/modules/postfix_sender.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8639612161f1474e2e47d89fe5ca4337e43e20de185e914ac4124cb0da932a15", + "format": 1 + }, + { + "name": "plugins/modules/package.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7dbd6a79af80902a974648e4280ee75bef0a3f104311e8bd39c2c0209b9e8ea2", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "385f5e3859e45188171d945e21314d40dca46b63e9a870f6dc73434bae1668ff", + "format": 1 + }, + { + "name": "plugins/modules/hasync_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9f932f65ed76fd5b190c9f76b54441ffb51561e5b30cb6f9987c707ce0d93d01", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_pool.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a25c5ecb9abc654d59388b6af82241f734aea60a7433ff8a2acfd7fd2bf78470", + "format": 1 + }, + { + "name": "plugins/modules/interface_gre.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ad0e96e0e3c85d38a02c0f45e8641ba2944daf4515e576d8678b2aae10375b71", + "format": 1 + }, + { + "name": "plugins/modules/bind_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b913cd35214138167da2654cf0ea77185b795472dac6c4aec4e90b80db0ec099", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "24a23685f53a971dc783212373e75781f23a7ad8c2b503560868e48dec376cd0", + "format": 1 + }, + { + "name": "plugins/modules/hasync_service.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b2ea1ed1674993e5d0646044b5c1cec7a065c762d59de5248c22146edae2b14", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf_prefix_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d50c3e5ef2571ff928f210a18f5e7846bdc1a02ddb6db8d205b6707263d6e37e", + "format": 1 + }, + { + "name": "plugins/modules/dhcp_reservation.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7ff9d3b295fe0895ca26d613f9b1563256f76501b20cc15affc2683000ce875b", + "format": 1 + }, + { + "name": "plugins/modules/ids_user_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d776b472122fd20bd6acbbaaa18939a565c6e06aeb9033539159c43a040c1880", + "format": 1 + }, + { + "name": "plugins/modules/monit_service.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5b38d9f0f04958b9116f0e55c16605d75152c4c619b344d542add13dfd755b88", + "format": 1 + }, + { + "name": "plugins/modules/dnsmasq_boot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a66371af6ad8feefcfd8396d9f219ae5607b58657a4237194dce52f73fbd257", + "format": 1 + }, + { + "name": "plugins/modules/interface_vip.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b60147601e1c712c2d74b176646fad649b2d18301812d6fd848d9f706fd90bc4", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp_neighbor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "42fbfd309377a4e4464d7aa6f5da691a1217a7cdbe3db524257bc4ab4de0d216", + "format": 1 + }, + { + "name": "plugins/modules/nat_source.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e149fd260ac5dd09196da665e7b01c83ff0be188241f199dad93fd0612668236", + "format": 1 + }, + { + "name": "plugins/modules/ids_policy_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f3c7289ee40932c79a72de0acae391e0873acdcc821feea1e77b8f2aa2d0821a", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_general_cache.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "769d81de6f85def441c36bd31a983b7f05ea3b11285b1dee56ebdcb14f02ef1a", + "format": 1 + }, + { + "name": "plugins/modules/openvpn_client.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ecb7a1363559e9d4e2a1333dcf60d27598345ad421fa8fc95344b10996215aa", + "format": 1 + }, + { + "name": "plugins/modules/rule_interface_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7698f37f97d07e0d5ede8ab5cb7e0c570e658df36703d7702e93058b4ea138a8", + "format": 1 + }, + { + "name": "plugins/modules/unbound_host.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "65abd67142d93aac853789cb5f7936db0f89e26217c486e6966c87357dbf5942", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_traffic.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8d3ddc79200236ea4ea6b33eecce105e58364082bd7badf7212a2f56463f9fc9", + "format": 1 + }, + { + "name": "plugins/modules/postfix_headercheck.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4097ff84581f3fd0743220e5f2d4dc776b5e947dace93b185547e8b8504e5708", + "format": 1 + }, + { + "name": "plugins/modules/acme_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "80e83cf2949395f9ef290722249dc7edb315f083eb61627383bb57b9b0199fb1", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_pac_match.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8485d8a073059255a7df79ef981aeaa1b67c545c2ae750ae24f551eebc619980", + "format": 1 + }, + { + "name": "plugins/modules/service.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cb7ef82605a5332f5b061d74405dd2126ff42ca04690825eaa722beecb7227c7", + "format": 1 + }, + { + "name": "plugins/modules/acme_action.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "13b2abfda834bcc6738c8b607c661ad122458fabd01edc0da9aa5c45574059ec", + "format": 1 + }, + { + "name": "plugins/modules/user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c6c20f9ae57fdad8922c3e291c65fa85e82bd40e66970624cce2086d647c71b1", + "format": 1 + }, + { + "name": "plugins/modules/bind_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "51e6debf37c33eb11985f16e2c97b120a1a33cd0dddd9c25c42eddcbbc205b10", + "format": 1 + }, + { + "name": "plugins/modules/reload.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "302e0bc8365522c0048f347722aaa03646b21bcb16667c591d4d609d08bf2dad", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_auth_local.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "487240ae4da4b27f1e6dce5e1fc8d8e2dc675e64b84caa8aa1c9e47934aec8a8", + "format": 1 + }, + { + "name": "plugins/modules/interface_lagg.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f834f40130ace8b6273a46abe67003ac50e99e6861333bbdebf35254ee431944", + "format": 1 + }, + { + "name": "plugins/modules/wireguard_peer.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6d6a5b9abe0f6a70a11fde9a02faa02bf6474b0f7643b960f8cec839c3bec9da", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_forward.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "857bc0e09e498d41f45468191ba4268e6af18507bb8e96259e8bf2fd8b56d431", + "format": 1 + }, + { + "name": "plugins/modules/nginx_upstream_server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "20b9c07e29ef50c1b56cd9e5cfbe59f6a70fbe82799c3fc09968185f4632fdfb", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7fdb0fddbbd3314dc16aa0ea18889263297505ce7fc66c67f2a1330c6304f728", + "format": 1 + }, + { + "name": "plugins/modules/dhcrelay_destination.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c27e40b5617a12938be95dbe72ace3a9f27b5f4571fbfe61f8daf38f1b861413", + "format": 1 + }, + { + "name": "plugins/modules/dnsmasq_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d33eb24f1cdd94571e63c5331b71a8f0a69be59a0daad396ba5f71e2d5facb1d", + "format": 1 + }, + { + "name": "plugins/modules/openvpn_static_key.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "adb6be6dd563491792ce90796171d5a8f24c23ff75b25f5c0394b94c3779002d", + "format": 1 + }, + { + "name": "plugins/modules/openvpn_server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9d059bcb77fd61afed4a4004ee77eeed458cbd836cc40de310eec8b8ed18fd2e", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_manual_spd.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f4375f1276e7abd0878fb8bd1d83c2ff95dcc77859a392d2fe300bc501553c0d", + "format": 1 + }, + { + "name": "plugins/modules/alias.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ec593639bd97c70889a1ecdbab9ed666bcac49d4816fe24575b7235d78ba0a53", + "format": 1 + }, + { + "name": "plugins/modules/shaper_queue.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9ec9827809e06c48936b756e0e187ea5b4eb810b08281cf48329b53a5a046498", + "format": 1 + }, + { + "name": "plugins/modules/ids_policy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "384614044abf97e53b856c5338a68a14c3a1b36795df3ac87f95e20e88ba9261", + "format": 1 + }, + { + "name": "plugins/modules/frr_bfd_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e9f8e4e57a74ec8ac0295427eb0baf2365ee1351365f369231ca8ff80a2e810c", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd8d3128e98e98e7161ac641fbef34282f0991bd914567510b09438ff84dcf3e", + "format": 1 + }, + { + "name": "plugins/modules/unbound_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c47a1c86740b826fa2648b281211e2aba5cda59659b1b7bdd20a25180596fa3a", + "format": 1 + }, + { + "name": "plugins/modules/dnsmasq_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f478105095096565d634ca1d815525a9530ac4b310c4284b2c0ed7d83ee8f297", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_errorfile.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "18327ef78d02ea6f00c906facb4a780ebcf65f0559509555b5c0fe365ddead75", + "format": 1 + }, + { + "name": "plugins/modules/postfix_recipient.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b4e8d946cdc9b498d26bd0e05ab5421a539b8af8986a619c7f30eb96b0acb0fd", + "format": 1 + }, + { + "name": "plugins/modules/bind_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a9d5429ece37efa3372028593b5af2012375c0b44ba4dbc9c4fd1143218d0832", + "format": 1 + }, + { + "name": "plugins/modules/monit_alert.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7fe2ce8e226a4a6998dc21aca948b4c6c1def488766b0dacbfdf53f6dc917de2", + "format": 1 + }, + { + "name": "plugins/modules/alias_purge.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f1cd762f55b0eec022fe9ef2971f89a8038c531f58a990db2feb67977927d5f5", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_lua.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9a8e7460c2bbc4f340238a231d84e2190b667260ad5528e4c78eedd4c0f86484", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_auth_remote.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8e509dfe1b48cb6369544844042af89ceaa0fea7490b1b9eb5e2a41dec9be230", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf3_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4d2961d699a74daf1471399297bf29ac440d3769a86cfa2d28a3f2d769392bbc", + "format": 1 + }, + { + "name": "plugins/modules/raw.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "93ed38e70f0596697af99cb6662ad5be2884aaf5a866c0dd66799a99b2065bb5", + "format": 1 + }, + { + "name": "plugins/modules/rule_multi.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b141160038e9229f6562d265480ca1064b5c9c8b2e928a55e395b78e0fb57626", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "39439c19c90632fd97fcd07a5b2e406efe1bc7f94ce52365de845b00102d6c7f", + "format": 1 + }, + { + "name": "plugins/modules/alias_multi.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "31d4dc5c73de87891b4c22c40e1b40048ed9d3425da55aeea9ac205b42e83596", + "format": 1 + }, + { + "name": "plugins/modules/interface_gif.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5884f5b9b602ed6fd92363014070fca9e620a900407adb03efebe731a4b2e7ff", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf_redistribution.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e441a2d82f8487b97ed880c584c309e8fd2cab9271e6ce1e471bec57009102f7", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf3_route_map.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "015977a404959db13bb81751ec2544723b5cdfe52f1c52d60af17a330be34f02", + "format": 1 + }, + { + "name": "plugins/modules/dhcrelay_relay.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6d954d74dac7c6e6dd15ac873a3738be217f9d089d90082e20145dba9540582d", + "format": 1 + }, + { + "name": "plugins/modules/postfix_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2811f93e44a058c5baec3e8d4c35a47bd446421bc389e6acb3412c0a85c3d9f3", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp_prefix_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dc35e811dc1f34770673976844d84b0732446afc7ea74eaa22607aab78cbdcea", + "format": 1 + }, + { + "name": "plugins/modules/frr_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "daf25ae5483eb56112a446cd86255cb58e88f9e78c9ab15c2fcd95716f04e695", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf3_interface.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "73670a2fabe5206c2d273baee3f876f0974f683c1b1dd90b29c6c497d0fd74c7", + "format": 1 + }, + { + "name": "plugins/modules/bind_record.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9e102bf01b184d5c9a0c9ccf66d345e8c91aa12fa8d10be30bfe74e34d9c57bc", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "da95f8766877e25133fcad1792df34b50abc4dfe26178bd66f88ade9b013b7f4", + "format": 1 + }, + { + "name": "plugins/modules/list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd15fe65f21cce6438f64a4e6412556f34086db4745b77b9541992c557a5ef71", + "format": 1 + }, + { + "name": "plugins/modules/nginx_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7442ff77ce4857681d7d5a42543a8924710906aa49b50bc477f6ff3d46b112dd", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp_community_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "716684101f66d4ff884a55fed2b964c1f245041d2f79763a08974b44b2510e13", + "format": 1 + }, + { + "name": "plugins/modules/openvpn_client_override.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "67f3bfebe68a2389a7b1bad1128ca9a2c119d1756cd83255a77adcfe4c5dede2", + "format": 1 + }, + { + "name": "plugins/modules/unbound_dot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2e7ac7db1b7829667aae16bd7d721e49f38f56ea2c733f241f42be8cc65c2d27", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6bc304c936a1bffd2f23f451b0c825925d1d7649d9988804a048aa4013ffcd8f", + "format": 1 + }, + { + "name": "plugins/modules/openvpn_status.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "23fb138614b120036b7b0f06b2c4cc11c148a5495f4e0214e1cd5317149e0be9", + "format": 1 + }, + { + "name": "plugins/modules/dnsmasq_range.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b6dbef7f89ce2457f269219d62a553a8464d4eda4b7468f4c8fe1c02e117f193", + "format": 1 + }, + { + "name": "plugins/modules/acme_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f352e9f2cdbb72fcea7962570ecefe3cb4ec9151f713caf83260f4b609720751", + "format": 1 + }, + { + "name": "plugins/modules/acme_validation.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a8d622144ec8293f5a4a0a5f667c8dcbe83864d49ff620712424c9ea415eb32", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_vti.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a07cfea42c52ab3a6267fe7c1cacb75b0ccdbddd903d12843c86e2e25917e274", + "format": 1 + }, + { + "name": "plugins/modules/dnsmasq_option.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "97677da53f01dc62b2d976546b9619b36fb90d3582c168d7c6045e29f0d5b552", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp_peer_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a6ce182629b65a1cb00e597a509e1388ad17389b26abb2ea69de8f4ad0e60ae6", + "format": 1 + }, + { + "name": "plugins/modules/gateway.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8632c1750408d35edc37c48ed6834aa788652ed3fd3bbc86dae9394506507331", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf3_redistribution.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dc90e5ac557d50ab16145035ea704f9f5ec3d6d2e4542585c0202579b595bf63", + "format": 1 + }, + { + "name": "plugins/modules/unbound_dnsbl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0f5543abdc2c48a4b83979daa5ee16a8d64df4e13bd279d800e9513b88b22d0b", + "format": 1 + }, + { + "name": "plugins/modules/interface_vxlan.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5c1721454349f0830f1f34b95f4cc85f0f719fc82b520ca74e65bde4658b264a", + "format": 1 + }, + { + "name": "plugins/modules/wireguard_server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a65ec11692cafea93872df4124a71c314380ad1afc21b6ed09e13c3a94815ce7", + "format": 1 + }, + { + "name": "plugins/modules/cron.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5924879c5c36535d41dd588d31b443020d97b3d46ac6815be11d9f376408f14d", + "format": 1 + }, + { + "name": "plugins/modules/frr_bfd_neighbor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b7779d0b5cccbe27a59af62a9708c4a3ef27785057f29fd4f484e662474e35e3", + "format": 1 + }, + { + "name": "plugins/modules/group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ad04411f3d5362bc8b4e9c6514bee474de830d6bedb14342c0b35ac02de2df18", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf3_prefix_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "13492d900cbb643ac49d5c0a034905d4682e0a747df84e89a01e501c8ed80f87", + "format": 1 + }, + { + "name": "plugins/modules/unbound_forward.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cd66df20a675726548f3ffe4d308db57a77e72e4b854d5852676fe580aa06df7", + "format": 1 + }, + { + "name": "plugins/modules/interface_vlan.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "87e1bca943d78c5c7d9c7860d10fe0a00a3f445dc0586fb9e5190fe72187a28a", + "format": 1 + }, + { + "name": "plugins/modules/shaper_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cfecab26c93555f660982d8249650aa369d96c11ed51a197eb87d57492554528", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_general_settings.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8d6f56d300b385f961c423177cedd0fe227f5f8ee654d14f684121dee8582ad0", + "format": 1 + }, + { + "name": "plugins/modules/rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "497093e4b1f6939a8be5e55bc5a3b02164c30727fc12f2130fd8664bfd20236f", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_general_peers.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "82e88c6092dcbc6650e89e0f2283e852f0b1f7250cbef9681a7a69ef5aa0d5c8", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_psk.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b522bf3c1ce2ac8b13a7f2d5fd3b3cc8959dde64dedf4d256a05bf79a32c7c74", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_general_logging.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a3dfd796caa11c09059014ee0592933608a2c19d76b1eabb42012c8da0dd8ab", + "format": 1 + }, + { + "name": "plugins/modules/postfix_senderbcc.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8818b450d730339fec0ee791bc59bb78a9e2777f25f52917b20193b9544a50c3", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_cpu.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "82c562c05915451127422dac2b307714db18bc0bcd4048a06ca1831c09e6cf47", + "format": 1 + }, + { + "name": "plugins/modules/postfix_sendercanonical.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "797e70c13e7f395a755a8edc9d23be28e042638e43de90a72196dae3835786f3", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_general_tuning.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ebfb7d12312b109ac41396dceb68e3bac5bcabafd2c946aa633e304ddcc0c83", + "format": 1 + }, + { + "name": "plugins/modules/frr_rip.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "361e9758d3f37f264087dfcb5ac86f79329a4993b6f058e1a986d73a23b65a57", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_pac_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9708eab5254dd8ed9498d07f682bc602519ed4d4b4e296d307fc1fde1219ef11", + "format": 1 + }, + { + "name": "plugins/modules/bind_record_multi.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1b29c688741e2971ec3b449716dd2edcec88e7465c193723a1e8203ac631a4b3", + "format": 1 + }, + { + "name": "plugins/modules/dnsmasq_host.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3a21111a9726afab9319ec9632b34f4e380f93139a9c27718da6e2062b0e6eb", + "format": 1 + }, + { + "name": "plugins/modules/neighbor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1fda5427b3b564280f57953d8fca0acfb83df218244bea45605edb746f50fd11", + "format": 1 + }, + { + "name": "plugins/modules/nat_one_to_one.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2b8cf46ff4d9dcd26c1ecbcc3f0aa8eeec74532d65415cd9beeb74ea9d2861a3", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp_redistribution.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a6b6531ecc057c90e3e7590a3d5767d411a760830a1e01507dbd44a2a5ac75d", + "format": 1 + }, + { + "name": "plugins/modules/frr_diagnostic.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "cc4eb59ef8bf0aafa0becfa05ee68f40dc617c9d57074845be1fa33523219236", + "format": 1 + }, + { + "name": "plugins/modules/ids_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ad636df6df9e9d10b75afd90169695f2773623d7df9b4f3fa251c999fa74072", + "format": 1 + }, + { + "name": "plugins/modules/unbound_host_alias.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4ae3a1029ca5aabc4909a67169f9014da168fb2ccf72f00502b7f915928b1e0c", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf_interface.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a2509570e75937cec011142d2216bc194ccd6bc23324abd4b8a10b72d1367851", + "format": 1 + }, + { + "name": "plugins/modules/wazuh_agent.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0f9d5e35ee8c09c9f960ba40ee7ec56ed39a9c5410d2b22f50c006e3ff21fd56", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_icap.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2b12cfb1ec278d9e5d9014b158e759600f32d1ec55d87a0bfadf05279dfcf2e4", + "format": 1 + }, + { + "name": "plugins/modules/ids_ruleset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "440345d2334eec1cac0f51e872dd625a8e488edb41d998abfd80a00a5147f5ca", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_pac_proxy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ac784ce72430fead5f09c9ea5f547156de6e27eba10e7e6a34258d0b2ecea09", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf_route_map.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "35592ac5d6b22c720e081075646a3e41aa51ef5fbc9ac4c3e2bf1ce7267880ce", + "format": 1 + }, + { + "name": "plugins/modules/monit_test.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c259159972fd834827ec9545acc7dd4bfbe8f224cf54456e1b3f59ca2076c098", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_general_stats.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "70df0cb26359d959dac5b8e25d192be1edd823ab57af70d8fef615898baf9a88", + "format": 1 + }, + { + "name": "plugins/modules/system.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f0da3a0f50936526438148534f8f3d3a7893c4d5487e2a34c439b516c616dc65", + "format": 1 + }, + { + "name": "plugins/modules/webproxy_cache.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6dbc07048c9f58ff55abd94682ffbb3118c48cb692091883bafc82791d9e4eda", + "format": 1 + }, + { + "name": "plugins/modules/snapshot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "994268491affe3596dd18aadfa80d4426164f0e2dbdc9f50653ef73a584f4e92", + "format": 1 + }, + { + "name": "plugins/modules/wireguard_show.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a6ea88c0407e5a6161b2394b36ab0e05dbaf6afc820de888003beb6f5a4443ff", + "format": 1 + }, + { + "name": "plugins/modules/frr_ospf_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "30d532dc5236ea9ada0bb1bb78bec188ff8aef9d8fc15b44d25757ab8e8ce5f5", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp_as_path.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9e0fcae529353a96159dd4ec171c9302e326a22e0c9acf262a1ff9aefc570f03", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_maintenance.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "120acc4bec49e07b691f1db0d1a9776e985962db00720cfac439b6852775fb88", + "format": 1 + }, + { + "name": "plugins/modules/ipsec_connection.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c583b4ee8a9b2a35a1f5acfc7a21df2dc03a1df5cffeeabf6c056bb9e85b7210", + "format": 1 + }, + { + "name": "plugins/modules/rule_purge.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3d86dd6ad5536746c70e9ac3596f484f57ea7305520482bdd9c49e01ee10e241", + "format": 1 + }, + { + "name": "plugins/modules/frr_bgp_route_map.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "870f54510175ababde7f2d294873b77d69543780be43101da361958a3a1f2ca8", + "format": 1 + }, + { + "name": "plugins/modules/dnsmasq_tag.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c0c6e98ca56a3251d6857ffdf9e332aed8a1b06af9ce49d00df0a0128545e26c", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_action.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "926fdffcf0fd4c82ca0bf5f9c6c09da6f2d51749ff499cc8ff7b33aedadaee2f", + "format": 1 + }, + { + "name": "plugins/modules/postfix_address.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1831045bc97d85cc7b9a0918fed249d433ef38e2c55e602280c657f0668d1126", + "format": 1 + }, + { + "name": "plugins/modules/route.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7e7ed16d7482ccb90cb0ac99eab13d099e6a9255541deb3e1c0ac45f87a54f54", + "format": 1 + }, + { + "name": "plugins/modules/haproxy_general_defaults.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "377b99aa732aec3c04c65f70327dd58518c0c1f77853d42f1e94de41bfa54d16", + "format": 1 + }, + { + "name": "plugins/modules/savepoint.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "05bdbe022586637bc913e6825c6387d6b31e15e879a988b8ca6f90d72465e273", + "format": 1 + }, + { + "name": "plugins/module_utils", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/defaults", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/defaults/legacy_multi.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08f1b66587cdda32afe7171c114581425441b65cc67ae47a03b2caff31a15055", + "format": 1 + }, + { + "name": "plugins/module_utils/defaults/main.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e21814cf21d41901878a9ff1401a39afdf724fbaadb56025c730680549e212f3", + "format": 1 + }, + { + "name": "plugins/module_utils/defaults/alias.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "eed9f8516697fd23a128408db52442e5efe127314bedfde39e5f4a83a3fa981e", + "format": 1 + }, + { + "name": "plugins/module_utils/defaults/openvpn.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "787c0c8b847d2bd04a420224d5f8b95a21788de402fc7bf88cc0cd45db9f49b1", + "format": 1 + }, + { + "name": "plugins/module_utils/defaults/bind.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "671bc49c90ae018c33fa67207f0137207d9cbb9b341e990dc669428b1e581015", + "format": 1 + }, + { + "name": "plugins/module_utils/defaults/ipsec_auth.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3167edfa35964a05e5e6c0510f8838ef5ed234b74d319da35b463e9df3f393a7", + "format": 1 + }, + { + "name": "plugins/module_utils/defaults/rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "08dd96997e37c0754f4daf95b47fd47326dfb63bdfc7252bc968ea1d2b280051", + "format": 1 + }, + { + "name": "plugins/module_utils/test", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/test/testdata", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/main", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_recipientbcc.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "67111d6ee479d579408199162552062c37eded5fbbf51c0caf966542e437a7f0", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_remote_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ec4690385b81b16536275bc70d51b0f53cdd94050a07e5195a569feabf9a37f3", + "format": 1 + }, + { + "name": "plugins/module_utils/main/interface_loopback.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1660a1b2530957c3e780e82375ddac891cf0408383d4552516be4ac79782c7a4", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3a88ac0cb63189e93aed4be96bf7d3faac85d0282e40f3c9fbf0b9cac9469a96", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_auth.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "07049a7f274ebeced4848d70341aab441e53d44b95d46f40ce11fec7850e7600", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dhcp_controlagent.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8a12cc4a236cb123c96a529d8f52a9da03d06fdfaab28f9cc7ca6772bcf476d9", + "format": 1 + }, + { + "name": "plugins/module_utils/main/bind_blocklist.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "07d58595d508a5b5994dad52ba19567f6bd9619ebea5854354b27fe6e19a050f", + "format": 1 + }, + { + "name": "plugins/module_utils/main/interface_bridge.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d6bcefd0484390a0b0ea154f309c4c0441cc9bf175c13ef7d8a913aa8bfdeea7", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_cert.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "78624ba842d0b8d1a9abe9b50f900ab7320b9cc2c3a02cefde1ee3ca3cf39242", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf3_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "121817348c08e2e08b726bdeb937218b04295bec664edaf06ba82e66e434e51c", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_fcgi.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1ef927c47e267c62be551562f165f251275a6d722b894c9f7f4ad8d28760b0b1", + "format": 1 + }, + { + "name": "plugins/module_utils/main/acme_account.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ffd4ad6a13410e718762601c431d8fa233e7d43ec22d5393798e4ab44d470fbf", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_parent.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5a323a8abd1b21ee9d7bcba75c1ca4879fc1c8f0316e0fd996ad6d60120d7c99", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_child.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e6c82f635bf5819e8ef807162b895ccb8b4fa5f0bc1bbf774f3423e0caf9cfdb", + "format": 1 + }, + { + "name": "plugins/module_utils/main/shaper_pipe.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "39160760064ebaf5d0f3bc9006e14621440a719b234b646eb44ec2e491ec6705", + "format": 1 + }, + { + "name": "plugins/module_utils/main/privilege.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b25b4c6490d56722a882de62e0f22823549e0120250d458ecd3a6c3fd4add3ce", + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "105f777d1cacb1d2a1ee1f33f881e3bcd9240f7b735d38d8fa4d6167ffa63008", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dhcp_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "90f4c8b0606955b6e8d4979a7db879293a5dc5a434ad45fc9541b7d0a30d908f", + "format": 1 + }, + { + "name": "plugins/module_utils/main/syslog.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "741d8ed279b5049ea4f4b9859a9b629201c9f1899322505c91b6599e55c2bdfd", + "format": 1 + }, + { + "name": "plugins/module_utils/main/unbound_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fad02d7eed7a64af3a480a2a9ae44af519da77a79b90b01978c034097dfeca41", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ids_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6460cf2c99a4ea538ba5853480111f79f41bd785a18fab6d57cd29bcce1528ab", + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_sender.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "de87af63f32e9d11d8a271547d2fb49fda4ac69419dbb2275e37a7b97af85bc2", + "format": 1 + }, + { + "name": "plugins/module_utils/main/package.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "37161ed512efc64af66218df4f3d688181f04b1fac8c4ca87260491bdc626c0d", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e79ff8354d1c99808835982112790225b389e5013e383f67cc00eff0f01971f", + "format": 1 + }, + { + "name": "plugins/module_utils/main/hasync_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "05c83630f7b3f9df69443b1f42930f2502b4050510bf3e40b00ab7c500af1235", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_pool.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8249242f24a5a360f47153114b45443d46f83eecc5966c054af027984fe7957e", + "format": 1 + }, + { + "name": "plugins/module_utils/main/interface_gre.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fd1317daec1e913e7c82822240d3aedb78c452c4ce94af54616cf8b6012492d8", + "format": 1 + }, + { + "name": "plugins/module_utils/main/bind_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "82f0b0ddf3fee8fe77b3c6f0c83e716f6013828c9c4131857697edbaee0bce6a", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6dd6333ce6bd76e8a97b9c74b9ba63a1e1d24fe573a90a72cd95f4997b211a63", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf_prefix_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2dfb38965cbcbc1cfb5ee48b80413ab27c2780e9efa21c582fc6dd14f1f32116", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ids_user_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0ab0214b17f514f065da98735f0a7a533da8f211fb9c914608d88f1457277f64", + "format": 1 + }, + { + "name": "plugins/module_utils/main/monit_service.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "274f3bc1efdc58308907a4b0cf974e35423b06d983c35c81644988a77c27aaca", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dnsmasq_boot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "501eaf5123ba820230e210e23e674bae36688c644a764db72bc21e7ef39b90fa", + "format": 1 + }, + { + "name": "plugins/module_utils/main/interface_vip.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "59b715a772f9b0331ee51177a9d8cb75903304dde161da8e420c5864947a537e", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bgp_neighbor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8ebd3a4fb42d076cd8965b4476da71cdb4c1f58b393b39b24c1209f88a9047e5", + "format": 1 + }, + { + "name": "plugins/module_utils/main/nat_source.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5f1e22d003c2036d320defd870d4d4e7561981f51b8710518c83a719094cb934", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ids_policy_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c8aa0006d880b2be2e93f08f2294d9af985ace168e525817c5f259cc88350c97", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_general_cache.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "741505451797d774cee0881a07abc4b297b3d5129c97e62e2c8cb31e6cea72d2", + "format": 1 + }, + { + "name": "plugins/module_utils/main/openvpn_client.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "20f224a289e6ef5425ecbdbba9157f4e9c387ddc0eab054e440ea7986f452f74", + "format": 1 + }, + { + "name": "plugins/module_utils/main/rule_interface_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5fdef5180a2ec890dc8705cc839beae63414cb60bfa6420757ecebfb3bc9abae", + "format": 1 + }, + { + "name": "plugins/module_utils/main/unbound_host.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bf23c03c12c8a24ea1358800707fc1637601393bd96383ed0c0c62ff505c5e95", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_traffic.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7984b00dfc57af214ef19d41218e251284899bcaa90e753b0ef8838dca714c41", + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_headercheck.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ab32014481ec30bbc0824f746d10280f9625cec495d459f49ae6ccd092e07285", + "format": 1 + }, + { + "name": "plugins/module_utils/main/acme_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "59cd1b6de58e0d544786839c66f5abe7852ade9edb468239c932295fa0f868be", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_pac_match.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "531d705004ec86d7187ab175718de37a94a8ac8cb1b248d868d03dabd7e0665b", + "format": 1 + }, + { + "name": "plugins/module_utils/main/acme_action.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c43276e47b6bffc4371ebeabe90b55b148543c385d875fa86273936ac7010542", + "format": 1 + }, + { + "name": "plugins/module_utils/main/user.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6ec0c82f099281d472a209b824f885938673ce1fc21c111fd3a9cbec0f20d3ce", + "format": 1 + }, + { + "name": "plugins/module_utils/main/bind_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "01f731b1d9a3f40d5695f8419d01604df37f8d623f119219bc600059203effd4", + "format": 1 + }, + { + "name": "plugins/module_utils/main/unbound_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c9e94a5184b4e0c310a8e59495cd335ef2bb5b5963f6498018efcbc86bab855d", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_auth_local.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fe021cf856f134f49f8ad39d924ac73700f5914bb28872df55ccbb66d1d3ccd0", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dhcp_subnet_v4.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "22a204189c3a544b895c65bb51ab4d4f21cecece6f9bcaf5cfc030a99e659b78", + "format": 1 + }, + { + "name": "plugins/module_utils/main/interface_lagg.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "56df982cd42d2a4d825e3071c148ccf699b63608d43c05610abb4a57f09b9499", + "format": 1 + }, + { + "name": "plugins/module_utils/main/wireguard_peer.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "31920aca36ab4d101db469b91974463484e3917a3ae27b4fe0e9f9ea72a8894b", + "format": 1 + }, + { + "name": "plugins/module_utils/main/package_main.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "13925799fd0e357c16f2fbfc4d0be9ae493a71b385c811bba3b7f7f0f0c90ede", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_forward.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ece4ef666198a340eca2400b183e0a7b799c63b68e8dee51f7fed7b7aa54ef9b", + "format": 1 + }, + { + "name": "plugins/module_utils/main/nginx_upstream_server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c5241605ddc4aeb93d88251af09c2f257bdbd39835b7bcf4ca381424abb6d007", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e4c6189ff38aad925c00fd0e59aad8dc0425da8ebc0035c1d0fa627327b3b154", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dhcrelay_destination.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c45a48b7c217e59fef19f2fdb4ef16d7bf36c9a708558b7420f6eb5d3cbf9483", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dnsmasq_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "037ad619b001f51287fd3ec5a17d4a20e2749b113c5ed8d46a0782cee07af422", + "format": 1 + }, + { + "name": "plugins/module_utils/main/openvpn_static_key.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "db44dca2f3c25abb9203a46cf623171717025fce48a94d29c23d58fade6a12f8", + "format": 1 + }, + { + "name": "plugins/module_utils/main/openvpn_server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "40f6ba759deb86a3ded9e64c997c06125cb2332adb5d8a3e33b812b0caa75692", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_manual_spd.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f365419e7b8a0854cb4f3d932529ff4cee54b7044f2e485b7120ff285bd133f5", + "format": 1 + }, + { + "name": "plugins/module_utils/main/alias.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "382898775fe8410e54ab2cef0fde681b0f50a984242d500102193a3f2c70c7bd", + "format": 1 + }, + { + "name": "plugins/module_utils/main/shaper_queue.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a4a467547115bd7aa72b4c2e04d5e5a7336ada7b459b38f16eb89c4d291765d0", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ids_policy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9ddc543314c092f5b0f09bb06dbe654200a7fccda14d19f56d68635ac494dc0d", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4df5cf9831a675a4d091c51066f82188d89ff347b561bc515b2a6beb467d96a0", + "format": 1 + }, + { + "name": "plugins/module_utils/main/unbound_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2ea4d4b597fc088b0461c5dca59da834e33af3dab841887783bd7da456da5fa3", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dnsmasq_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e59bcd8de9953a4c7b6f42098db48d5d096f5a7db27f2ccfa70c72f571fd4395", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_errorfile.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "43d493325f7cba3eeb15a90e5a6be2c9d3ba5c4daa873291ed6ae96a1f9d9314", + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_recipient.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d9b43142fa11c7d3b086584f0f6e80aa15dfeb7f386d9aa2efbc8bf1091bc620", + "format": 1 + }, + { + "name": "plugins/module_utils/main/bind_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "07db9524fa20c42a63cc2347eddeb1a89757c288f90e3ef040cf71be9b91adb1", + "format": 1 + }, + { + "name": "plugins/module_utils/main/monit_alert.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bb3982776669a3cff97700115492a1065e2756b8e9f36c27de65abb600c57cc3", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_lua.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a623bf929913792eb3013694ec10cb96c023f47ebb6642f7aba0b328483557c1", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_auth_remote.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9aeef9636c2c34713e466f0ba0a739bd79a12b56e03ec1f5d92479b62232799e", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf3_network.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "fd06e23c6b01f72f5810ed86f8fd75b7c02a99bea039d234d1c0a0d1d14713a8", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_acl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f3cbe3a708b22cf3910f53e7c301f7daae59e04e837bca8b0e719c427d2c5f43", + "format": 1 + }, + { + "name": "plugins/module_utils/main/interface_gif.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bbc7921eae75991cc2b3b562680a792533822e25a31a6372da4dc5def9de5f47", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf_redistribution.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "341e6ba4d3a26fad6d7d90b6582e8b00122f866a066e54d70a1dd06d83b34b0c", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf3_route_map.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bbb67f1553043d89831c631df8afa481ad8bca02c1c4b97a09594b32f3fcd7fd", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dhcrelay_relay.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3b5525dd24550f14147a3355f986182ef9691ad7c4128f101a9f899b62389c05", + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_domain.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e046ce0a8ce6db5a16284317b7f1c3fb6b6142587f9aeb4d61d81921ce7f8ab3", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bgp_prefix_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "25843b286eed45bb9456d30a7890cdedb0187148a3e9ec37da8e9ec898e04ef7", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ae7db87b4138a76452720b6d3db17777ed67a34400ff13eff452384a92a3f4ab", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf3_interface.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d27ea2bd0b3563ce363041e6b852359805f73b1013403e411d3613a18afdd2d0", + "format": 1 + }, + { + "name": "plugins/module_utils/main/bind_record.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "0a486a0c9856f5aa11530c73cf5489559a5e98ff19dfb6f8cc677fa99d7e2226", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dc19683ea4d42afa6b50eda97b3f38c9bb1b2a5bcab349fc566f68ba92194f16", + "format": 1 + }, + { + "name": "plugins/module_utils/main/nginx_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e9a15e39d47ded4e4188260c5955ed5e213fab22e0b8f17fc4771d625b178763", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bgp_community_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "11dbd2cf88236a54e54bdcec63295a80f42973a6ddf54c6e4b0e7793c1647aa9", + "format": 1 + }, + { + "name": "plugins/module_utils/main/openvpn_client_override.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "23668092f6f5e603d6735567214b311552a0358a97b44a564aeda7004b4594bd", + "format": 1 + }, + { + "name": "plugins/module_utils/main/unbound_dot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5fc0a75cd6ee803eb2a40bc11d49fdfec6bdd5551b778632adbc7a2322386007", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bgp_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "b10718ec451900b4156f67a9707b0310a5d2880265bc1ac824aca65aaf6865c9", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dnsmasq_range.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4b06da298ff89e981f1e4920e0cf6676a25da52edbeb2e2754fa07f727fddf79", + "format": 1 + }, + { + "name": "plugins/module_utils/main/acme_certificate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60cec8fad6c021d28c6917066796832ec34c72aafcb498e94094fd31990cd43a", + "format": 1 + }, + { + "name": "plugins/module_utils/main/acme_validation.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1e0382c39deb86154c527cc2d60756d8bb40fd3ea8f616e6bf73499483365cfb", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_vti.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "92312b28e26bb551181b5113c28f9d04e1b8fdbf6a9678a5f249dcf8a48d54f7", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dnsmasq_option.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "1a9af1df27517ff34dcb40bae3a87eefea3636e49836711a48b840ab235dd616", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bgp_peer_group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "55d6df039c4fb740902d7ef244c014f2112a56cca99b52b8c5f2be0c8304e7c8", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_auth.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ceb01560c9f652f2d941dfc6dded228ac85ebfb3e160bfed7c660cc74a10a688", + "format": 1 + }, + { + "name": "plugins/module_utils/main/gateway.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e0558465f1f115ae85ea3bcbf0c6b6617ea04daaec50a60d2d5164ce4366ddb3", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf3_redistribution.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "803b9593fdab0c49ef4dfd966e2a82c4a140f7e212c73ec65b897254c2eab904", + "format": 1 + }, + { + "name": "plugins/module_utils/main/unbound_dnsbl.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "34df99f8101e33d96dd6f4df3a13d124ad615c17065f4a1983610a46329524ab", + "format": 1 + }, + { + "name": "plugins/module_utils/main/interface_vxlan.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d70fde35f1cb5ee3e8e7dc9d180e2085639905a7bae67d1cec9f1947256868f8", + "format": 1 + }, + { + "name": "plugins/module_utils/main/wireguard_server.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6281956832d6a000924436a2e2f62a15daa632c540523f015449bd0a1d6c1c5b", + "format": 1 + }, + { + "name": "plugins/module_utils/main/cron.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6eb3efffb2779ec4ef561d24798e6ad1419f4159696ee7e6ab21d03a17d7d922", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bfd_neighbor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "dd38e49322f6cdccc00f728cefc4137c343cc4c1a48889fb8b9c3851fb631a0c", + "format": 1 + }, + { + "name": "plugins/module_utils/main/group.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "df83b598d414ac25981871aa7761c268623f2a93b251fda11acb59eb965159ed", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf3_prefix_list.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8aae4d3f1935a2d10e0071f206b997fd696a211eb809b8a2c7cd3c8735f5bf8b", + "format": 1 + }, + { + "name": "plugins/module_utils/main/unbound_forward.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "44cb8feef9aadca832f0060b7783f2fe3f152548a481192523b1d353b6134009", + "format": 1 + }, + { + "name": "plugins/module_utils/main/interface_vlan.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3609281840e03cc641733cc6065401406b0cbbbe830d3c93c5da7c9cfb3e2528", + "format": 1 + }, + { + "name": "plugins/module_utils/main/shaper_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c3835fe6fcc43b1845059cc037ab124c9ebb3a92d528ab1f6977a09acad62516", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_general_settings.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f70fbf5e9bfc99293a9595606822288ea74f21c8332972798f776bd65418a2c5", + "format": 1 + }, + { + "name": "plugins/module_utils/main/rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bbe81631d7b41133eb65738159b88dc47552f1fb101a756242bde455f1cc617b", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_general_peers.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "8c978f2593490ee3408681cf61575835eba5c07a43f55ca3fbc398b4b468a107", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_psk.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "81d37a57a81b2e5e317b5ced012be6bdcf90989ca409f363ed5c7f651ef6783f", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_general_logging.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "28ea4e94476c54238f822e83d220f587727f2cb1301bf85e102d6ca0d35c0fd5", + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_senderbcc.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "457f6a3b2130cbf0cf6f870398b999da73bd56e9c801d70332ad3c81dfee22da", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_cpu.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "479af19478f75ffc85d8f7372aede19fbe6e76ebd5a94d7f44194a5310865f21", + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_sendercanonical.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "4debb9409b41a4e2307ae58cad74a450a96b82c302f30af1e3a8599ab7643ead", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_general_tuning.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "29dfdf3f70349354593358015c6b3210e9347aa8a150902c6e7fa0472661bb4d", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_rip.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6a11a378a07410c775c472599b3b1160afe21cd8f3d9101dddbb78349b7a7d0b", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_pac_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "754ade459f128376c1332d08f33d80123500666d8c8249bbaa3973004b4b338e", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dnsmasq_host.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2229b081aced7679d747384031d24201f5213b8fc73fb7aedc88bbfb3e28e54f", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dhcp_reservation_v4.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "815f401b624a46530ad63efdff77ca708c2b4baea7c987ab7d1767d2590bb3ee", + "format": 1 + }, + { + "name": "plugins/module_utils/main/neighbor.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c9858ccf6980b890d47916f3f5ab75db046b267e41e31fea540d09738eb1edec", + "format": 1 + }, + { + "name": "plugins/module_utils/main/nat_one_to_one.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "382c2a5b2d604ce54488712272dc3ca08a84fcadaf2fb61e68ada755d6c73f4d", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bgp_redistribution.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "aa4549d2517637cbc2f6bd0b1376ba592b80cb43f2b9d3a59b1a9eb7ed6a06f1", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ids_rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "402c8ffa71cb9de7e4a311f81ab7e479b04b6ca2055f48dff7ac4b87867e7245", + "format": 1 + }, + { + "name": "plugins/module_utils/main/unbound_host_alias.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f73b5e7c2c80a36145096dfeb2bc715082e772eb5b481c34196164149eb53c8f", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf_interface.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d938b2b293fecef0b508b57a3f5b76425c03ea9cdcd980bb39acfccb96174753", + "format": 1 + }, + { + "name": "plugins/module_utils/main/wazuh_agent.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7ade15f18e236494866401e7d23de38a698812d59f8711813b2ca06f20e8670c", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_icap.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "927ef84feae0dabb27153d7d526190113318ae6695a4eee5880442cc7a2d7e47", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ids_ruleset.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f680fe71e1479f4c942584ac8f73c73cfd7f389652a3e35da44a4e3a9d03050d", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_pac_proxy.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2819b652e62eb9583cc6bf12680eb41c2c0a216a9e2107731ea79343fd2532ad", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf_route_map.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "158488cf09a7362bef8b4e4225302d510c08d629e8fb5325eb7985d03acd2efe", + "format": 1 + }, + { + "name": "plugins/module_utils/main/monit_test.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a8eca608382045d17437e84a8405aeee9f2ec20744f6e6d22ca55e2cd45669be", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_general_stats.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "723b42e08ce1b1573726a801f1e5fe2ac1da728d82b1f727dbb2c7619077f65d", + "format": 1 + }, + { + "name": "plugins/module_utils/main/webproxy_cache.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "38177615555f0ff056a415c56521d654a96cd5666a914d07815ea900a5c992bc", + "format": 1 + }, + { + "name": "plugins/module_utils/main/snapshot.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f12c1ba419f887828aae6a78aba9938a766507636f60e6d54df9abdafa73b032", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_ospf_general.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "49659c5a551dd8b63a884354adb1f7d45a8f59fcc7e18fb7ba3d9733b93290a8", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bgp_as_path.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "60cade833fb05bb14e92513f3981d3207bd4b65425e85d8427a90dc12fa24437", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_maintenance.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "f194368d7e1a124e39ccfb8ee558e55d27af2e6756d1e9702b331e4a55c88b50", + "format": 1 + }, + { + "name": "plugins/module_utils/main/ipsec_connection.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3578f89051b3eb16ccedcde304d2dd2da06dc335fa76ac170f7370e84a4a0837", + "format": 1 + }, + { + "name": "plugins/module_utils/main/frr_bgp_route_map.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "bfcd993e0c71d6d756e6c9b7742c80eda9d52140dd40930e05b883eaeb222cf3", + "format": 1 + }, + { + "name": "plugins/module_utils/main/dnsmasq_tag.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "321acde2f0d386e6722d0ded10b71316eb8d9d4466262176077730cf5b932f1e", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_action.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "5fbd328d9bbcc326c950d4eb93044f175d728e4819680c289e101eea9d924ee1", + "format": 1 + }, + { + "name": "plugins/module_utils/main/postfix_address.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "05fa808725fd576a6b059b4207b8262a1db99689d8d42f08c9a9ea8be5fc7d07", + "format": 1 + }, + { + "name": "plugins/module_utils/main/route.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3eeaba0d1cbf88da87cb19369c12623821edb6a604a2cb7c581afda1517abf72", + "format": 1 + }, + { + "name": "plugins/module_utils/main/haproxy_general_defaults.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2a6321cdcf060966d56dcf14d4de99b3f08291bfa4644f9939ce2c525a4bd48e", + "format": 1 + }, + { + "name": "plugins/module_utils/main/savepoint.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "586a284378f0104504561a1d27b11b2c9015f0cbad3afb58f40c747dc9a212f8", + "format": 1 + }, + { + "name": "plugins/module_utils/base", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/base/base.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "112503e51b8e626fbb988b2291be273b599a1d0814cbd20332592e19e6a50557", + "format": 1 + }, + { + "name": "plugins/module_utils/base/cls.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "e3fa7275805d9dac0b87b0506d5312fea20e9bd39a8c4e40408c1d6b0227c205", + "format": 1 + }, + { + "name": "plugins/module_utils/base/wrapper.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "c19a9f3c0150908e242eb0925385cec7a2097fdce8063d29f43577b3d89f7798", + "format": 1 + }, + { + "name": "plugins/module_utils/base/multi.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "93f9a9d2ce39a23c44f42f78add34e0cdaad843308ba4b100460c6371c72dead", + "format": 1 + }, + { + "name": "plugins/module_utils/base/handler.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "771f948ffd44bfab0802f4bc02b44144b76cf892938074c450ea9316226b8876", + "format": 1 + }, + { + "name": "plugins/module_utils/base/api.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "2b012bdd4a03ccb7cabde3433bb2c3d034ffdef0f28167945a769f09b606350c", + "format": 1 + }, + { + "name": "plugins/module_utils/helper", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "plugins/module_utils/helper/unbound.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "72c3603becd566b0a9f71daad4e89764a4015aef6990e7c423e477c36fb4bc39", + "format": 1 + }, + { + "name": "plugins/module_utils/helper/validate.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "a2ad8dc76a829715afcc47028a79f90f607406ca4099c1560ff21fabf2ec524d", + "format": 1 + }, + { + "name": "plugins/module_utils/helper/utils.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7972c45490cfb07013866592d1975d2f4873a229994fc2aad10eb5fdb3f3dfbb", + "format": 1 + }, + { + "name": "plugins/module_utils/helper/main.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "ba75eb43a5fc4a10d24ea673f80e6ddb138b4000a44250d87104ad83c37a31f7", + "format": 1 + }, + { + "name": "plugins/module_utils/helper/alias.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "d46cb12fc62f11cceadf7895a98d8ee3771f68e6b52707d42161c168f9607561", + "format": 1 + }, + { + "name": "plugins/module_utils/helper/rule.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "6e5b23777ba61dabd742e8ee9e7480f560a7c8bfb31f32ee36c2de149f4b9559", + "format": 1 + }, + { + "name": "plugins/module_utils/helper/system.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "9825fc08171cb72223b306b1540ce4e977483823b562e424544b9c3db60bbc2d", + "format": 1 + }, + { + "name": "plugins/module_utils/helper/api.py", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "48495b92a3afd4e8f431a7099f573f79d27a8d029dd4bd5123ba8d72ba8df37c", + "format": 1 + }, + { + "name": "plugins/module_utils/inline_docs", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta", + "ftype": "dir", + "chksum_type": null, + "chksum_sha256": null, + "format": 1 + }, + { + "name": "meta/runtime.yml", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "7f87e5d7a2d5453d35cf19ef6b8c4e96864e7c228cad1c26cfe68e57e5eaf45d", + "format": 1 + } + ], + "format": 1 +} \ No newline at end of file diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/LICENSE.txt b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/LICENSE.txt new file mode 100644 index 0000000..7e4ebd2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/LICENSE.txt @@ -0,0 +1,641 @@ + 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 + + + Ansible Collection to manage OPNsense firewalls using its API. + Copyright (C) 2025 OXL IT Services + + 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 . + + E-Mail: contact+opnsense@OXL.at + Web: https://www.OXL.at diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/MANIFEST.json b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/MANIFEST.json new file mode 100644 index 0000000..3fee563 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/MANIFEST.json @@ -0,0 +1,36 @@ +{ + "collection_info": { + "namespace": "oxlorg", + "name": "opnsense", + "version": "25.7.8", + "authors": [ + "OXL IT Services " + ], + "readme": "README.md", + "tags": [ + "firewall", + "api", + "opnsense", + "iac", + "network", + "security", + "filter" + ], + "description": "Ansible Collection to manage OPNsense firewalls using its API", + "license": [], + "license_file": "LICENSE.txt", + "dependencies": {}, + "repository": "https://github.com/O-X-L/ansible-opnsense", + "documentation": "https://ansible-opnsense.oxl.app", + "homepage": "https://www.OXL.at", + "issues": "https://github.com/O-X-L/ansible-opnsense/issues" + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": "3002a7e33b186084479c7a4765503e022dbf6d438d9de1548793d1845a8d4352", + "format": 1 + }, + "format": 1 +} \ No newline at end of file diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/README.md b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/README.md new file mode 100644 index 0000000..55a72b8 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/README.md @@ -0,0 +1,265 @@ +# Ansible Collection to manage OPNsense Firewalls + +

+ + Support Badge (Donate, Support-Licenses) + +

+ +---- + +[![Lint Python](https://github.com/O-X-L/ansible-opnsense/actions/workflows/lint-python.yml/badge.svg)](https://github.com/O-X-L/ansible-opnsense/actions/workflows/lint-python.yml) +[![Lint Ansible](https://github.com/O-X-L/ansible-opnsense/actions/workflows/lint-ansible.yml/badge.svg)](https://github.com/O-X-L/ansible-opnsense/actions/workflows/lint-ansible.yml) +[![Unit Test Status](https://github.com/O-X-L/ansible-opnsense/actions/workflows/unit_test.yml/badge.svg)](https://github.com/O-X-L/ansible-opnsense/actions/workflows/unit_test.yml) +[![Ansible Galaxy](https://badges.oss.oxl.app/galaxy.badge.svg)](https://galaxy.ansible.com/ui/repo/published/oxlorg/opnsense) + +**Functional Tests**: + +* Status: [![Functional Test Status](https://badges.oss.oxl.app/oxlorg.opnsense.collection.test.svg)](https://github.com/O-X-L/ansible-opnsense/actions/workflows/functional_test_result.yml) | +[![Functional-Tests](https://github.com/O-X-L/ansible-opnsense/actions/workflows/functional_test_result.yml/badge.svg)](https://github.com/O-X-L/ansible-opnsense/actions/workflows/functional_test_result.yml) +* Logs: [API](https://ci.oss.oxl.app/api/job/ansible-test-collection-opnsense/logs?token=2b7bba30-9a37-4b57-be8a-99e23016ce70&lines=1000) | +[Daily Archive](https://github.com/O-X-L/ansible-opnsense/actions/workflows/functional_test_result.yml) + +Internal CI: [Tester Role](https://github.com/O-X-L/ansible-role-oxl-cicd) | [Jobs API](https://github.com/O-X-L/github-self-hosted-jobs-systemd) + +---- + +## Requirements + +The [httpx python module](https://www.python-httpx.org/) is used for API communications! + +```bash +python3 -m pip install --upgrade httpx +``` + +Then - install the collection itself: + +```bash +# latest version: +ansible-galaxy collection install git+https://github.com/O-X-L/ansible-opnsense.git + +# stable/tested version: +ansible-galaxy collection install git+https://github.com/O-X-L/ansible-opnsense.git,25.7.8 +## OR +ansible-galaxy collection install oxlorg.opnsense +``` + +---- + +## Usage + +See: [Docs](https://ansible-opnsense.oxl.app) + +[![Docs Uptime](https://status.oxl.at/api/v1/endpoints/1--oxl_opnsense-ansible-collection-docs/uptimes/7d/badge.svg)](https://status.oxl.at/endpoints/1--oxl_opnsense-ansible-collection-docs) + +If you DO NOT want to use Ansible - [this fork](https://github.com/O-X-L/opnsense-api-client) provides you with a raw Python3 interface. + +---- + +## Support the project(s) + +Support the Open-Source projects that make these modules possible: + +* [Donate to OPNsense](https://opnsense.org/donate/) or [Buy the Business-Edition](https://shop.opnsense.com/product-categorie/software_and_licenses/) +* [Donate to the Ansible-Module Maintainers](https://shop.oxl.app/products/open-source-spende) or [Buy a Support-License](https://shop.oxl.app/products/open-source-support-opnsense-ansible-collection) + +---- + +## Contribute + +Feel free to contribute to this project using [pull-requests](https://github.com/O-X-L/ansible-opnsense/pulls), [issues](https://github.com/O-X-L/ansible-opnsense/issues) and [discussions](https://github.com/O-X-L/ansible-opnsense/discussions)! + +See also: [Contributing](https://github.com/O-X-L/ansible-opnsense/blob/latest/CONTRIBUTING.md) + + + +---- + +## Version Support + +We try that the `oxlorg.opnsense` modules always support the latest version of OPNsense. + +If an API changed, the current module-implementation might fail for firewalls running an older firmware. + +As [this project is unfunded](https://github.com/O-X-L/ansible-opnsense/discussions/199) we do not actively check for API-changes - if you find missing functionalities you need/want to have please [report it](https://github.com/O-X-L/ansible-opnsense/issues)! + +---- + + +## Modules + +**Development States**: + +not implemented => development => [testing](https://github.com/O-X-L/ansible-opnsense/tree/latest/tests) => [unstable (_practical testing_)](https://github.com/O-X-L/ansible-opnsense/discussions/85) => stable + +### Implemented + + +| Function | Module | Usage | State | +|:--------------------------|:---------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------|:---------| +| **Base** | oxlorg.opnsense.list | [Docs](https://ansible-opnsense.oxl.app/general/list.html) | stable | +| **Base** | oxlorg.opnsense.reload | [Docs](https://ansible-opnsense.oxl.app/general/reload.html) | stable | +| **Raw** | oxlorg.opnsense.raw | [Docs](https://ansible-opnsense.oxl.app/general/raw.html) | unstable | +| **Services** | oxlorg.opnsense.service | [Docs](https://ansible-opnsense.oxl.app/general/service.html) | stable | +| **Alias** | oxlorg.opnsense.alias | [Docs](https://ansible-opnsense.oxl.app/modules/alias.html) | stable | +| **Alias** | oxlorg.opnsense.alias_multi | [Docs](https://ansible-opnsense.oxl.app/modules/alias_multi.html) | stable | +| **Alias** | oxlorg.opnsense.alias_purge | [Docs](https://ansible-opnsense.oxl.app/modules/alias_multi.html#oxlorg-opnsense-alias-purge) | unstable | +| **Rules** | oxlorg.opnsense.rule | [Docs](https://ansible-opnsense.oxl.app/modules/rule.html) | stable | +| **Rules** | oxlorg.opnsense.rule_multi | [Docs](https://ansible-opnsense.oxl.app/modules/rule_multi.html) | stable | +| **Rules** | oxlorg.opnsense.rule_purge | [Docs](https://ansible-opnsense.oxl.app/modules/rule_multi.html#oxlorg-opnsense-rule-purge) | unstable | +| **Rule Interface Groups** | oxlorg.opnsense.rule_interface_group | [Docs](https://ansible-opnsense.oxl.app/modules/rule_interface_group.html#oxlorg-opnsense-rule-interface-group) | stable | +| **Savepoints** | oxlorg.opnsense.savepoint | [Docs](https://ansible-opnsense.oxl.app/modules/savepoint.html) | stable | +| **Packages** | oxlorg.opnsense.package | [Docs](https://ansible-opnsense.oxl.app/modules/package.html) | stable | +| **System** | oxlorg.opnsense.system | [Docs](https://ansible-opnsense.oxl.app/modules/system.html) | stable | +| **Cron-Jobs** | oxlorg.opnsense.cron | [Docs](https://ansible-opnsense.oxl.app/modules/cron.html) | stable | +| **Routes** | oxlorg.opnsense.route | [Docs](https://ansible-opnsense.oxl.app/modules/routing.html) | stable | +| **Gateways** | oxlorg.opnsense.gateway | [Docs](https://ansible-opnsense.oxl.app/modules/routing.html) | stable | +| **DNS** | oxlorg.opnsense.unbound_general | [Docs](https://ansible-opnsense.oxl.app/modules/unbound_general.html) | stable | +| **DNS** | oxlorg.opnsense.unbound_acl | [Docs](https://ansible-opnsense.oxl.app/modules/unbound_acl.html) | stable | +| **DNS** | oxlorg.opnsense.unbound_forward | [Docs](https://ansible-opnsense.oxl.app/modules/unbound_forwarding.html) | stable | +| **DNS** | oxlorg.opnsense.unbound_dot | [Docs](https://ansible-opnsense.oxl.app/modules/unbound_dot.html) | stable | +| **DNS** | oxlorg.opnsense.unbound_host | [Docs](https://ansible-opnsense.oxl.app/modules/unbound_host.html) | stable | +| **DNS** | oxlorg.opnsense.unbound_host_alias | [Docs](https://ansible-opnsense.oxl.app/modules/unbound_host_alias.html) | stable | +| **DNS** | oxlorg.opnsense.unbound_dnsbl | [Docs](https://ansible-opnsense.oxl.app/modules/unbound_host_alias.html) | stable | +| **Syslog** | oxlorg.opnsense.syslog | [Docs](https://ansible-opnsense.oxl.app/modules/syslog.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_connection, oxlorg.opnsense.ipsec_tunnel | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_pool, oxlorg.opnsense.ipsec_network | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_auth_local | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_auth_remote | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_child | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_vti | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_cert | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_psk | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.ipsec_manual_spd | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | stable | +| **IPSec** | oxlorg.opnsense.general | [Docs](https://ansible-opnsense.oxl.app/modules/ipsec.html) | unstable | +| **Traffic Shaper** | oxlorg.opnsense.shaper_pipe | [Docs](https://ansible-opnsense.oxl.app/modules/shaper.html) | stable | +| **Traffic Shaper** | oxlorg.opnsense.shaper_queue | [Docs](https://ansible-opnsense.oxl.app/modules/shaper.html) | stable | +| **Traffic Shaper** | oxlorg.opnsense.shaper_rule | [Docs](https://ansible-opnsense.oxl.app/modules/shaper.html) | stable | +| **Monit** | oxlorg.opnsense.monit_service | [Docs](https://ansible-opnsense.oxl.app/modules/monit.html) | stable | +| **Monit** | oxlorg.opnsense.monit_alert | [Docs](https://ansible-opnsense.oxl.app/modules/monit.html) | stable | +| **Monit** | oxlorg.opnsense.monit_test | [Docs](https://ansible-opnsense.oxl.app/modules/monit.html) | stable | +| **WireGuard** | oxlorg.opnsense.wireguard_server | [Docs](https://ansible-opnsense.oxl.app/modules/wireguard.html) | stable | +| **WireGuard** | oxlorg.opnsense.wireguard_peer | [Docs](https://ansible-opnsense.oxl.app/modules/wireguard.html) | stable | +| **WireGuard** | oxlorg.opnsense.wireguard_show | [Docs](https://ansible-opnsense.oxl.app/modules/wireguard.html) | stable | +| **WireGuard** | oxlorg.opnsense.wireguard_general | [Docs](https://ansible-opnsense.oxl.app/modules/wireguard.html) | stable | +| **Interfaces** | oxlorg.opnsense.interface_vlan | [Docs](https://ansible-opnsense.oxl.app/modules/interface.html) | stable | +| **Interfaces** | oxlorg.opnsense.interface_vxlan | [Docs](https://ansible-opnsense.oxl.app/modules/interface.html) | stable | +| **Interfaces** | oxlorg.opnsense.interface_vip | [Docs](https://ansible-opnsense.oxl.app/modules/interface.html) | stable | +| **Interfaces** | oxlorg.opnsense.interface_lagg | [Docs](https://ansible-opnsense.oxl.app/modules/interface.html) | stable | +| **Interfaces** | oxlorg.opnsense.interface_loopback | [Docs](https://ansible-opnsense.oxl.app/modules/interface.html) | stable | +| **Interfaces** | oxlorg.opnsense.interface_gre | [Docs](https://ansible-opnsense.oxl.app/modules/interface.html) | stable | +| **Interfaces** | oxlorg.opnsense.interface_bridge | [Docs](https://ansible-opnsense.oxl.app/modules/interface.html) | unstable | +| **Interfaces** | oxlorg.opnsense.interface_gif | [Docs](https://ansible-opnsense.oxl.app/modules/interface.html) | unstable | +| **NAT** | oxlorg.opnsense.nat_source | [Docs](https://ansible-opnsense.oxl.app/modules/source_nat.html) | stable | +| **NAT** | oxlorg.opnsense.nat_one_to_one | [Docs](https://ansible-opnsense.oxl.app/modules/one_to_one.html) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_diagnostic | [Docs](https://ansible-opnsense.oxl.app/modules/frr_diagnostic.html) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_general | [Docs](https://ansible-opnsense.oxl.app/modules/frr_general.html) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bfd_general | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bfd.html#oxlorg-opnsense-frr-bfd-general) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bfd_neighbor | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bfd.html#oxlorg-opnsense-frr-bfd-neighbor) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bgp_general | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-general) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bgp_neighbor | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-neighbor) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bgp_prefix_list | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-prefix-list) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bgp_route_map | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-route-map) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bgp_community_list | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-community-list) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bgp_as_path | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-as-path) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bgp_redistribution | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-redistribution) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_bgp_peer_group | [Docs](https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-peer-group) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf_general | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf-general) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf_prefix_list | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf-prefix-list) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf_route_map | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf-route-map) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf_interface | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf-interface) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf_network | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf-network) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf_redistribution | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf-redistribution) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf3_general | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf3-general) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf3_prefix_list | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf3-prefix-list) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf3_route_map | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf3-route-map) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf3_interface | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf3-interface) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf3_network | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf3-network) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_ospf3_redistribution | [Docs](https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf3-redistribution) | stable | +| **Dynamic Routing** | oxlorg.opnsense.frr_rip | [Docs](https://ansible-opnsense.oxl.app/modules/frr_rip.html) | stable | +| **DNS** | oxlorg.opnsense.bind_general | [Docs](https://ansible-opnsense.oxl.app/modules/bind.html#oxlorg-opnsense-bind-general) | stable | +| **DNS** | oxlorg.opnsense.bind_blocklist | [Docs](https://ansible-opnsense.oxl.app/modules/bind.html#oxlorg-opnsense-bind-blocklist) | stable | +| **DNS** | oxlorg.opnsense.bind_acl | [Docs](https://ansible-opnsense.oxl.app/modules/bind.html#oxlorg-opnsense-bind-acl) | stable | +| **DNS** | oxlorg.opnsense.bind_domain | [Docs](https://ansible-opnsense.oxl.app/modules/bind.html#oxlorg-opnsense-bind-domain) | stable | +| **DNS** | oxlorg.opnsense.bind_record | [Docs](https://ansible-opnsense.oxl.app/modules/bind.html#oxlorg-opnsense-bind-record) | stable | +| **DNS** | oxlorg.opnsense.bind_record_multi | [Docs](https://ansible-opnsense.oxl.app/modules/bind.html#oxlorg-opnsense-bind-record-multi) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_general | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id2) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_cache | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id3) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_parent | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id4) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_traffic | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id5) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_forward | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id7) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_acl | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id8) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_icap | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id9) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_auth | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id10) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_remote_acl | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id12) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_pac_proxy | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id14) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_pac_match | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id15) | stable | +| **Web Proxy** | oxlorg.opnsense.webproxy_pac_rule | [Docs](https://ansible-opnsense.oxl.app/modules/webproxy.html#id18) | stable | +| **IDS/IPS** | oxlorg.opnsense.ids_action | [Docs](https://ansible-opnsense.oxl.app/modules/ids.html#id2) | stable | +| **IDS/IPS** | oxlorg.opnsense.ids_general | [Docs](https://ansible-opnsense.oxl.app/modules/ids.html#id3) | stable | +| **IDS/IPS** | oxlorg.opnsense.ids_ruleset | [Docs](https://ansible-opnsense.oxl.app/modules/ids.html#id4) | stable | +| **IDS/IPS** | oxlorg.opnsense.ids_rule | [Docs](https://ansible-opnsense.oxl.app/modules/ids.html#id5) | stable | +| **IDS/IPS** | oxlorg.opnsense.ids_user_rule | [Docs](https://ansible-opnsense.oxl.app/modules/ids.html#id6) | stable | +| **IDS/IPS** | oxlorg.opnsense.ids_policy | [Docs](https://ansible-opnsense.oxl.app/modules/ids.html#id7) | stable | +| **IDS/IPS** | oxlorg.opnsense.ids_policy_rule | [Docs](https://ansible-opnsense.oxl.app/modules/ids.html#id8) | stable | +| **OpenVPN** | oxlorg.opnsense.openvpn_client | [Docs](https://ansible-opnsense.oxl.app/modules/openvpn.html) | stable | +| **OpenVPN** | oxlorg.opnsense.openvpn_server | [Docs](https://ansible-opnsense.oxl.app/modules/openvpn.html) | stable | +| **OpenVPN** | oxlorg.opnsense.openvpn_static_key | [Docs](https://ansible-opnsense.oxl.app/modules/openvpn.html) | stable | +| **OpenVPN** | oxlorg.opnsense.openvpn_status | [Docs](https://ansible-opnsense.oxl.app/modules/openvpn.html) | stable | +| **OpenVPN** | oxlorg.opnsense.openvpn_client_override | [Docs](https://ansible-opnsense.oxl.app/modules/openvpn.html) | stable | +| **Nginx** | oxlorg.opnsense.nginx_general | [Docs](https://ansible-opnsense.oxl.app/modules/nginx.html#oxlorg-opnsense-nginx-general) | stable | +| **Nginx** | oxlorg.opnsense.nginx_upstream_server | [Docs](https://ansible-opnsense.oxl.app/modules/nginx.html#oxlorg-opnsense-nginx-upstream-server) | stable | +| **DHCP Relay** | oxlorg.opnsense.dhcrelay_relay | [Docs](https://ansible-opnsense.oxl.app/modules/dhcrelay_relay.html) | stable | +| **DHCP Relay** | oxlorg.opnsense.dhcrelay_destination | [Docs](https://ansible-opnsense.oxl.app/modules/dhcrelay_destination.html) | stable | +| **DHCP** | oxlorg.opnsense.dhcp_general | [Docs](https://ansible-opnsense.oxl.app/modules/dhcp.html) | stable | +| **DHCP Subnet** | oxlorg.opnsense.dhcp_subnet | [Docs](https://ansible-opnsense.oxl.app/modules/dhcp.html) | stable | +| **DHCP Reservation** | oxlorg.opnsense.dhcp_reservation | [Docs](https://ansible-opnsense.oxl.app/modules/dhcp.html) | stable | +| **DHCP Controlagent** | oxlorg.opnsense.dhcp_controlagent | [Docs](https://ansible-opnsense.oxl.app/modules/dhcp.html) | stable | +| **ACME (Certificates)** | oxlorg.opnsense.acme_account | [Docs](https://ansible-opnsense.oxl.app/modules/acmeclient.html) | stable | +| **ACME (Certificates)** | oxlorg.opnsense.acme_action | [Docs](https://ansible-opnsense.oxl.app/modules/acmeclient.html) | stable | +| **ACME (Certificates)** | oxlorg.opnsense.acme_general | [Docs](https://ansible-opnsense.oxl.app/modules/acmeclient.html) | stable | +| **ACME (Certificates)** | oxlorg.opnsense.acme_validation | [Docs](https://ansible-opnsense.oxl.app/modules/acmeclient.html) | stable | +| **ACME (Certificates)** | oxlorg.opnsense.acme_certificate | [Docs](https://ansible-opnsense.oxl.app/modules/acmeclient.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_general | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_domain | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_recipient | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_recipientbcc | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_sender | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_senderbcc | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_sendercanonical | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_headercheck | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Postfix** | oxlorg.opnsense.postfix_address | [Docs](https://ansible-opnsense.oxl.app/modules/postfix.html) | stable | +| **Snapshot** | oxlorg.opnsense.snapshot | [Docs](https://ansible-opnsense.oxl.app/modules/snapshot.html) | stable | +| **High Availability** | oxlorg.opnsense.hasync_general | [Docs](https://ansible-opnsense.oxl.app/modules/hasync.html) | stable | +| **High Availability** | oxlorg.opnsense.hasync_service | [Docs](https://ansible-opnsense.oxl.app/modules/hasync.html) | stable | +| **User Management** | oxlorg.opnsense.user | [Docs](https://ansible-opnsense.oxl.app/modules/access.html) | unstable | +| **User Management** | oxlorg.opnsense.group | [Docs](https://ansible-opnsense.oxl.app/modules/access.html) | unstable | +| **User Management** | oxlorg.opnsense.privilege | [Docs](https://ansible-opnsense.oxl.app/modules/access.html) | unstable | +| **Neighbor** | oxlorg.opnsense.neighbor | [Docs](https://ansible-opnsense.oxl.app/modules/neighbor.html) | unstable | +| **Dnsmasq** | oxlorg.opnsense.dnsmasq_general | [Docs](https://ansible-opnsense.oxl.app/modules/dnsmasq.html) | unstable | +| **Dnsmasq** | oxlorg.opnsense.dnsmasq_domain | [Docs](https://ansible-opnsense.oxl.app/modules/dnsmasq.html) | unstable | +| **Dnsmasq** | oxlorg.opnsense.dnsmasq_host | [Docs](https://ansible-opnsense.oxl.app/modules/dnsmasq.html) | unstable | +| **Dnsmasq** | oxlorg.opnsense.dnsmasq_range | [Docs](https://ansible-opnsense.oxl.app/modules/dnsmasq.html) | unstable | +| **Dnsmasq** | oxlorg.opnsense.dnsmasq_option | [Docs](https://ansible-opnsense.oxl.app/modules/dnsmasq.html) | unstable | +| **Dnsmasq** | oxlorg.opnsense.dnsmasq_boot | [Docs](https://ansible-opnsense.oxl.app/modules/dnsmasq.html) | unstable | +| **Dnsmasq** | oxlorg.opnsense.dnsmasq_tag | [Docs](https://ansible-opnsense.oxl.app/modules/dnsmasq.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_general_cache | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_general_defaults | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_general_logging | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_general_peers | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_general_settings | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_general_stats | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_general_tuning | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_cpu | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_user | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_group | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_maintenance | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_acl | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_lua | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_action | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_errorfile | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | +| **HAProxy** | oxlorg.opnsense.haproxy_fcgi | [Docs](https://ansible-opnsense.oxl.app/modules/haproxy.html) | unstable | + +### Roadmap + +See: [Feature Requests](https://github.com/O-X-L/ansible-opnsense/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/meta/runtime.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/meta/runtime.yml new file mode 100644 index 0000000..3e786d2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/meta/runtime.yml @@ -0,0 +1,286 @@ +--- + +requires_ansible: ">=2.14" + +action_groups: + alias: + - oxlorg.opnsense.alias + - oxlorg.opnsense.alias_multi + - oxlorg.opnsense.alias_purge + rule: + - oxlorg.opnsense.rule + - oxlorg.opnsense.rule_multi + - oxlorg.opnsense.rule_purge + - oxlorg.opnsense.rule_interface_group + unbound: + - oxlorg.opnsense.unbound_general + - oxlorg.opnsense.unbound_acl + - oxlorg.opnsense.unbound_forward + - oxlorg.opnsense.unbound_dot + - oxlorg.opnsense.unbound_host + - oxlorg.opnsense.unbound_host_alias + - oxlorg.opnsense.unbound_dnsbl + ipsec: + - oxlorg.opnsense.ipsec_auth_local + - oxlorg.opnsense.ipsec_auth_remote + - oxlorg.opnsense.ipsec_child + - oxlorg.opnsense.ipsec_vti + - oxlorg.opnsense.ipsec_cert + - oxlorg.opnsense.ipsec_psk + - oxlorg.opnsense.ipsec_connection + - oxlorg.opnsense.ipsec_pool + - oxlorg.opnsense.ipsec_manual_spd + - oxlorg.opnsense.ipsec_general + shaper: + - oxlorg.opnsense.shaper_pipe + - oxlorg.opnsense.shaper_queue + - oxlorg.opnsense.shaper_rule + monit: + - oxlorg.opnsense.monit_service + - oxlorg.opnsense.monit_alert + - oxlorg.opnsense.monit_test + wireguard: + - oxlorg.opnsense.wireguard_server + - oxlorg.opnsense.wireguard_peer + - oxlorg.opnsense.wireguard_show + - oxlorg.opnsense.wireguard_general + interface: + - oxlorg.opnsense.interface_vlan + - oxlorg.opnsense.interface_vxlan + - oxlorg.opnsense.interface_vip + - oxlorg.opnsense.interface_lagg + - oxlorg.opnsense.interface_loopback + - oxlorg.opnsense.interface_gre + - oxlorg.opnsense.interface_bridge + - oxlorg.opnsense.interface_gif + - oxlorg.opnsense.neighbor + frr: + - oxlorg.opnsense.frr_diagnostic + - oxlorg.opnsense.frr_general + - oxlorg.opnsense.frr_bfd_general + - oxlorg.opnsense.frr_bfd_neighbor + - oxlorg.opnsense.frr_bgp_general + - oxlorg.opnsense.frr_bgp_prefix_list + - oxlorg.opnsense.frr_bgp_community_list + - oxlorg.opnsense.frr_bgp_as_path + - oxlorg.opnsense.frr_bgp_redistribution + - oxlorg.opnsense.frr_bgp_route_map + - oxlorg.opnsense.frr_bgp_neighbor + - oxlorg.opnsense.frr_bgp_peer_group + - oxlorg.opnsense.frr_ospf_general + - oxlorg.opnsense.frr_ospf_prefix_list + - oxlorg.opnsense.frr_ospf_interface + - oxlorg.opnsense.frr_ospf_redistribution + - oxlorg.opnsense.frr_ospf_route_map + - oxlorg.opnsense.frr_ospf_network + - oxlorg.opnsense.frr_ospf3_general + - oxlorg.opnsense.frr_ospf3_prefix_list + - oxlorg.opnsense.frr_ospf3_interface + - oxlorg.opnsense.frr_ospf3_redistribution + - oxlorg.opnsense.frr_ospf3_route_map + - oxlorg.opnsense.frr_ospf3_network + - oxlorg.opnsense.frr_rip + bind: + - oxlorg.opnsense.bind_general + - oxlorg.opnsense.bind_acl + - oxlorg.opnsense.bind_blocklist + - oxlorg.opnsense.bind_domain + - oxlorg.opnsense.bind_record + - oxlorg.opnsense.bind_record_multi + webproxy: + - oxlorg.opnsense.webproxy_general + - oxlorg.opnsense.webproxy_cache + - oxlorg.opnsense.webproxy_parent + - oxlorg.opnsense.webproxy_traffic + - oxlorg.opnsense.webproxy_forward + - oxlorg.opnsense.webproxy_acl + - oxlorg.opnsense.webproxy_icap + - oxlorg.opnsense.webproxy_auth + - oxlorg.opnsense.webproxy_remote_acl + - oxlorg.opnsense.webproxy_pac_proxy + - oxlorg.opnsense.webproxy_pac_match + - oxlorg.opnsense.webproxy_pac_rule + nginx: + - oxlorg.opnsense.nginx_general + - oxlorg.opnsense.nginx_upstream_server + route: + - oxlorg.opnsense.route + - oxlorg.opnsense.gateway + nat: + - oxlorg.opnsense.nat_source + - oxlorg.opnsense.nat_one_to_one + system: + - oxlorg.opnsense.list + - oxlorg.opnsense.reload + - oxlorg.opnsense.service + - oxlorg.opnsense.savepoint + - oxlorg.opnsense.package + - oxlorg.opnsense.system + - oxlorg.opnsense.cron + - oxlorg.opnsense.syslog + - oxlorg.opnsense.snapshot + ids: + - oxlorg.opnsense.ids_action + - oxlorg.opnsense.ids_general + - oxlorg.opnsense.ids_policy + - oxlorg.opnsense.ids_policy_rule + - oxlorg.opnsense.ids_rule + - oxlorg.opnsense.ids_ruleset + - oxlorg.opnsense.ids_ruleset_properties + - oxlorg.opnsense.ids_user_rule + openvpn: + - oxlorg.opnsense.openvpn_client + - oxlorg.opnsense.openvpn_server + - oxlorg.opnsense.openvpn_static_key + - oxlorg.opnsense.openvpn_status + - oxlorg.opnsense.openvpn_client_override + - oxlorg.opnsense.openvpn_client_template + - oxlorg.opnsense.openvpn_client_export + dnsmasq: + - oxlorg.opnsense.dnsmasq_general + - oxlorg.opnsense.dnsmasq_domain + - oxlorg.opnsense.dnsmasq_host + - oxlorg.opnsense.dnsmasq_range + - oxlorg.opnsense.dnsmasq_option + - oxlorg.opnsense.dnsmasq_boot + - oxlorg.opnsense.dnsmasq_tag + dhcrelay: + - oxlorg.opnsense.dhcrelay_destination + - oxlorg.opnsense.dhcrelay_relay + dhcp: + - oxlorg.opnsense.dhcp_reservation + - oxlorg.opnsense.dhcp_controlagent + - oxlorg.opnsense.dhcp_general + - oxlorg.opnsense.dhcp_subnet + acme: + - oxlorg.opnsense.acme_general + - oxlorg.opnsense.acme_account + - oxlorg.opnsense.acme_validation + - oxlorg.opnsense.acme_action + - oxlorg.opnsense.acme_certificate + postfix: + - oxlorg.opnsense.postfix_general + - oxlorg.opnsense.postfix_domain + - oxlorg.opnsense.postfix_recipient + - oxlorg.opnsense.postfix_recipientbcc + - oxlorg.opnsense.postfix_sender + - oxlorg.opnsense.postfix_senderbcc + - oxlorg.opnsense.postfix_sendercanonical + - oxlorg.opnsense.postfix_headercheck + - oxlorg.opnsense.postfix_address + hasync: + - oxlorg.opnsense.hasync_general + - oxlorg.opnsense.hasync_service + haproxy: + - oxlorg.opnsense.haproxy_general_settings + - oxlorg.opnsense.haproxy_general_cache + - oxlorg.opnsense.haproxy_general_defaults + - oxlorg.opnsense.haproxy_general_logging + - oxlorg.opnsense.haproxy_general_peers + - oxlorg.opnsense.haproxy_general_stats + - oxlorg.opnsense.haproxy_general_tuning + - oxlorg.opnsense.haproxy_maintenance + - oxlorg.opnsense.haproxy_cpu + - oxlorg.opnsense.haproxy_user + - oxlorg.opnsense.haproxy_group + - oxlorg.opnsense.haproxy_acl + - oxlorg.opnsense.haproxy_action + - oxlorg.opnsense.haproxy_lua + - oxlorg.opnsense.haproxy_fcgi + - oxlorg.opnsense.haproxy_errorfile + wazuh: + - oxlorg.opnsense.wazuh_agent + raw: + - oxlorg.opnsense.raw + access: + - oxlorg.opnsense.user + - oxlorg.opnsense.group + - oxlorg.opnsense.privilege + all: + - metadata: + extend_group: + - oxlorg.opnsense.alias + - oxlorg.opnsense.rule + - oxlorg.opnsense.unbound + - oxlorg.opnsense.ipsec + - oxlorg.opnsense.shaper + - oxlorg.opnsense.monit + - oxlorg.opnsense.wireguard + - oxlorg.opnsense.interface + - oxlorg.opnsense.frr + - oxlorg.opnsense.bind + - oxlorg.opnsense.webproxy + - oxlorg.opnsense.nginx + - oxlorg.opnsense.route + - oxlorg.opnsense.gateway + - oxlorg.opnsense.nat + - oxlorg.opnsense.system + - oxlorg.opnsense.ids + - oxlorg.opnsense.openvpn + - oxlorg.opnsense.dhcrelay + - oxlorg.opnsense.dhcp + - oxlorg.opnsense.acme + - oxlorg.opnsense.postfix + - oxlorg.opnsense.hasync + - oxlorg.opnsense.wazuh + - oxlorg.opnsense.raw + - oxlorg.opnsense.access + - oxlorg.opnsense.dnsmasq + - oxlorg.opnsense.haproxy + +plugin_routing: + modules: + ipsec_tunnel: + redirect: oxlorg.opnsense.ipsec_connection + ipsec_network: + redirect: oxlorg.opnsense.ipsec_pool + snat: + redirect: oxlorg.opnsense.source_nat + proxy_general: + redirect: oxlorg.opnsense.webproxy_general + proxy_cache: + redirect: oxlorg.opnsense.webproxy_cache + proxy_parent: + redirect: oxlorg.opnsense.webproxy_parent + proxy_traffic: + redirect: oxlorg.opnsense.webproxy_traffic + proxy_forward: + redirect: oxlorg.opnsense.webproxy_forward + proxy_acl: + redirect: oxlorg.opnsense.webproxy_acl + proxy_icap: + redirect: oxlorg.opnsense.webproxy_icap + proxy_auth: + redirect: oxlorg.opnsense.webproxy_auth + proxy_remote_acl: + redirect: oxlorg.opnsense.webproxy_remote_acl + proxy_pac_proxy: + redirect: oxlorg.opnsense.webproxy_pac_proxy + proxy_pac_match: + redirect: oxlorg.opnsense.webproxy_pac_match + proxy_pac_rule: + redirect: oxlorg.opnsense.webproxy_pac_rule + openvpn_client_overwrite: + redirect: oxlorg.opnsense.openvpn_client_override + routing: + redirect: oxlorg.opnsense.gateway + gw: + redirect: oxlorg.opnsense.gateway + rule_if_group: + redirect: oxlorg.opnsense.rule_interface_group + dhcrelay: + redirect: oxlorg.opnsense.dhcrelay_relay + dhcrelay_dst: + redirect: oxlorg.opnsense.dhcrelay_destination + unbound_domain: + redirect: oxlorg.opnsense.unbound_forward + acme_challenge: + redirect: oxlorg.opnsense.acme_validation + acme_automation: + redirect: oxlorg.opnsense.acme_action + source_nat: + redirect: oxlorg.opnsense.nat_source + one_to_one: + redirect: oxlorg.opnsense.nat_one_to_one + wireguard_client: + redirect: oxlorg.opnsense.wireguard_peer diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/api.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/api.py new file mode 100644 index 0000000..6b20214 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/api.py @@ -0,0 +1,143 @@ +from os import environ +from socket import setdefaulttimeout + +import httpx + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.api import \ + check_host, ssl_verification, check_response, get_params_path, debug_api, \ + check_or_load_credentials, api_pretty_exception +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import is_ip6 + +DEFAULT_TIMEOUT = 20.0 +HTTPX_EXCEPTIONS = ( + httpx.ConnectTimeout, httpx.ConnectError, httpx.ReadTimeout, httpx.WriteTimeout, + httpx.TimeoutException, httpx.PoolTimeout, +) + + +class Session: + def __init__(self, module: AnsibleModule, timeout: float = DEFAULT_TIMEOUT): + self.m = module + self.s = self._start(timeout) + + def _start(self, timeout: float) -> httpx.Client: + check_host(module=self.m) + api_key, api_secret = check_or_load_credentials(module=self.m) + if api_secret is None: + api_key = self.m.params['api_key'] + api_secret = self.m.params['api_secret'] + + if 'api_timeout' in self.m.params and self.m.params['api_timeout'] is not None: + timeout = self.m.params['api_timeout'] + + setdefaulttimeout(timeout) + + fw = self.m.params['firewall'] + if is_ip6(fw, strip_enclosure=False): + fw = f"[{fw}]" + + proxy = environ.get('HTTPS_PROXY', None) + if proxy is not None and not proxy.startswith('http') and not proxy.startswith('sock'): + proxy = None + + return httpx.Client( + base_url=f"https://{fw}:{self.m.params['api_port']}/api", + auth=(api_key, api_secret), + timeout=httpx.Timeout(timeout=timeout, connect=2.0), + transport=httpx.HTTPTransport( + verify=ssl_verification(module=self.m), + retries=self.m.params['api_retries'], + proxy=proxy, + ), + headers={'User-Agent': 'Ansible'} + ) + + def get(self, cnf: dict) -> dict: + params_path = get_params_path(cnf=cnf) + call_url = f"{cnf['module']}/{cnf['controller']}/{cnf['command']}{params_path}" + + debug_api( + module=self.m, + method='GET', + url=f'{self.s.base_url}{call_url}', + ) + + try: + response = check_response( + module=self.m, + cnf=cnf, + response=self.s.get(url=call_url) + ) + + except HTTPX_EXCEPTIONS as error: + api_pretty_exception( + m=self.m, method='GET', error=error, + url=f'{self.s.base_url}{call_url}', + ) + raise + + return response + + def post(self, cnf: dict, headers: dict = None) -> dict: + if headers is None: + headers = {} + + data = None + + if 'data' in cnf and cnf['data'] is not None and len(cnf['data']) > 0: + headers['Content-Type'] = 'application/json' + data = cnf['data'] + + params_path = get_params_path(cnf=cnf) + call_url = f"{cnf['module']}/{cnf['controller']}/{cnf['command']}{params_path}" + + debug_api( + module=self.m, + method='POST', + url=f'{self.s.base_url}{call_url}', + data=data, + headers=headers, + ) + + try: + response = check_response( + module=self.m, + cnf=cnf, + response=self.s.post( + url=call_url, json=data, headers=headers + ) + ) + + except HTTPX_EXCEPTIONS as error: + api_pretty_exception( + m=self.m, method='POST', error=error, + url=f'{self.s.base_url}{call_url}', + ) + raise + + return response + + def close(self) -> None: + self.s.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + +def single_get(module: AnsibleModule, cnf: dict, timeout: float = DEFAULT_TIMEOUT) -> dict: + with Session(module=module, timeout=timeout) as s: + response = s.get(cnf=cnf) + + return response + + +def single_post(module: AnsibleModule, cnf: dict, timeout: float = DEFAULT_TIMEOUT, headers: dict = None) -> dict: + with Session(module=module, timeout=timeout) as s: + response = s.post(cnf=cnf, headers=headers) + + return response diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/base.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/base.py new file mode 100644 index 0000000..8a1d230 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/base.py @@ -0,0 +1,750 @@ +# could be implemented using inheritance in the future.. + +# NOTE: pylint is basically right, but I really do not want to take the time to refactor this.. +# pylint: disable=W0212,R0912,R0915 + +from typing import Callable +from functools import reduce + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + single_get, single_post +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_simple_existing, to_digit, get_matching, simplify_translate, is_unset, \ + sort_param_lists +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + exit_bug, ModuleSoftError + + +class Base: + DIFF_FLOAT_ROUND = 1 + RESP_JOIN_CHAR = ',' + ATTR_JOIN_CHAR = 'JOIN_CHAR' + ATTR_AK_PATH = 'API_KEY_PATH' + ATTR_AK_PATH_REQ = 'API_KEY_PATH_REQ' # if a custom path depth is needed + ATTR_AK_PATH_GET = 'API_KEY_PATH_GET' # if 'get' needs custom path + ATTR_GET_ADD = 'SEARCH_ADDITIONAL' # extract additional data from search-call + ATTR_GET_DETAIL_ALL = 'SEARCH_DETAIL_ALL' + ATTR_AK_PATH_SPLIT_CHAR = '.' + ATTR_BOOL_INVERT = 'FIELDS_BOOL_INVERT' + ATTR_TRANSLATE = 'FIELDS_TRANSLATE' + ATTR_DIFF_EXCL = 'FIELDS_DIFF_EXCLUDE' + ATTR_DIFF_NO_LOG = 'FIELDS_DIFF_NO_LOG' + ATTR_VALUE_MAP = 'FIELDS_VALUE_MAPPING' + ATTR_VALUE_MAP_RCV = 'FIELDS_VALUE_MAPPING_RCV' + ATTR_FIELD_ALL = 'FIELDS_ALL' + ATTR_FIELD_CH = 'FIELDS_CHANGE' + ATTR_REL_CONT = 'API_CONT_REL' + ATTR_REL_CMD = 'API_CMD_REL' + ATTR_GET_CONT = 'API_CONT_GET' + ATTR_GET_MOD = 'API_MOD_GET' + ATTR_API_MOD = 'API_MOD' + ATTR_API_CONT = 'API_CONT' + ATTR_HEADERS = 'call_headers' + ATTR_TYPING = 'FIELDS_TYPING' + ATTR_FIELD_ID = 'FIELD_ID' # field we use for matching + ATTR_FIELD_PK = 'FIELD_PK' # field opnsense uses as primary key + ATTR_CMDS = 'CMDS' + PARAM_MATCH_FIELDS = 'match_fields' + QUERY_MAX_ENTRIES = 1000 + VALUE_NO_LOG = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER' + + REQUIRED_ATTRS = [ + ATTR_AK_PATH, + ATTR_TYPING, + ATTR_API_MOD, + ATTR_API_CONT, + ATTR_FIELD_ALL, + ATTR_FIELD_CH, + ATTR_CMDS, + ] + + def __init__(self, instance): + self.i = instance # module-specific object + self.e = {} # existing entry + self.raw = None # save first raw existing entry - to resolve user input per selection + + for attr in self.REQUIRED_ATTRS: + if not hasattr(self.i, attr): + exit_bug(f"Module has no '{attr}' attribute set!") + + def api_search_post(self, cnf: dict, data: dict = None) -> list: + if data is None: + data = {} + + return self._api_post({ + **cnf, + 'data': {'current': 1, 'rowCount': self.QUERY_MAX_ENTRIES, **data}, + })['rows'] + + def search(self, match_fields: list = None) -> (dict, list): + # workaround if 'get' needs to be performed using other api module/controller + cont_get, mod_get = self.i.API_CONT, self.i.API_MOD + + if hasattr(self.i, self.ATTR_GET_CONT): + cont_get = getattr(self.i, self.ATTR_GET_CONT) + + if hasattr(self.i, self.ATTR_GET_MOD): + mod_get = getattr(self.i, self.ATTR_GET_MOD) + + self.i.call_cnf['controller'] = cont_get + self.i.call_cnf['module'] = mod_get + + if self.i.CMDS['search'].startswith('search'): + # because of new OPNsense API: https://github.com/O-X-L/ansible-opnsense/issues/51 + if 'detail' not in self.i.CMDS: + exit_bug("To use the 'search' commands you need to also define the related 'detail' (get) command!") + + data = [] + # if we can - we only perform the 'detail' call for the already matched entry to save on needed requests + base_match_fields = False + base_match_fields_checked = False + force_details = getattr(self.i, self.ATTR_GET_DETAIL_ALL, False) + + for base_entry in self.api_search_post({ + **self.i.call_cnf, + 'command': self.i.CMDS['search'], + }): + if not force_details and match_fields is not None and not base_match_fields_checked: + base_match_fields_checked = True + base_match_fields = all(field in base_entry for field in match_fields) + + # todo: perform async calls for parallel data fetching + detail_entry = {} + if force_details or not base_match_fields or \ + all(base_entry[field] == self.i.p[field] for field in match_fields): + detail_entry = self._search_path_handling( + self._api_get({ + **self.i.call_cnf, + 'command': self.i.CMDS['detail'], + 'params': [base_entry[self.field_pk]] + }) + ) + if self.raw is None: + self.raw = detail_entry + + data.append({ + **base_entry, + **detail_entry, + }) + + if self.raw is None: + self.raw = self._search_path_handling( + self._api_get({ + **self.i.call_cnf, + 'command': self.i.CMDS['detail'], + }) + ) + + return data + + # legacy api handling (fewer requests needed; much simpler client-side handling) + data = self._api_get({ + **self.i.call_cnf, + 'command': self.i.CMDS['search'], + }) + + if hasattr(self.i, self.ATTR_GET_ADD): + for attr, ak_path in getattr(self.i, self.ATTR_GET_ADD).items(): + if hasattr(self.i, attr): + setattr( + self.i, attr, + self._search_path_handling(data=data, ak_path=ak_path) + ) + + return self._search_path_handling(data) + + def _search_path_handling(self, data: dict, ak_path: str = None) -> dict: + # resolving API_KEY_PATH's so data from nested dicts gets extracted as configured + if ak_path is None: + if hasattr(self.i, self.ATTR_AK_PATH_GET): + ak_path = getattr(self.i, self.ATTR_AK_PATH_GET) + + elif hasattr(self.i, self.ATTR_AK_PATH): + ak_path = getattr(self.i, self.ATTR_AK_PATH) + + try: + if ak_path is not None: + for k in ak_path.split(self.ATTR_AK_PATH_SPLIT_CHAR): + data = data[k] + + return data + + except KeyError: + exit_bug(f"Got invalid API_KEY_PATH: '{ak_path}' not matching data '{data}'") + + def get_existing(self, diff_filter: bool = False, details_all: bool = True) -> list: + if details_all: + # because of new OPNsense API: https://github.com/O-X-L/ansible-opnsense/issues/51 + # we require all details even if that means we have to perform hundreds of api-calls.. :( + setattr(self.i, self.ATTR_GET_DETAIL_ALL, True) + + if diff_filter: + # use already existing filtering to get 'clean' int/.. values + return get_simple_existing( + entries=self._call_search(), + simplify_func=self._call_simple(), + add_filter=self.build_diff, + ) + + return get_simple_existing( + entries=self._call_search(), + simplify_func=self._call_simple(), + ) + + def find(self, match_fields: list) -> None: + if self.i.existing_entries is None: + self.i.existing_entries = self._call_search(match_fields) + + match = get_matching( + module=self.i.m, existing_items=self.i.existing_entries, + compare_item=self.i.p, match_fields=match_fields, + simplify_func=self._call_simple(), + ) + + if match is not None: + setattr(self.i, self.i.EXIST_ATTR, match) + self.i.exists = True + self.i.r['diff']['before'] = self.build_diff(data=match) + + if self.field_pk in match: + self.i.call_cnf['params'] = [match[self.field_pk]] + + def process(self) -> None: + self.i.call_cnf['controller'] = self.i.API_CONT + self.i.call_cnf['module'] = self.i.API_MOD + + if 'state' in self.i.p and self.i.p['state'] == 'absent': + if self.i.exists: + if hasattr(self.i, 'delete'): + self.i.delete() + + else: + self.delete() + + else: + if 'state' not in self.i.p or self.i.exists: + if hasattr(self.i, 'update'): + self.i.update() + + else: + self.update() + + else: + if hasattr(self.i, 'create'): + self.i.create() + + else: + self.create() + + def create(self) -> dict: + self.i.r['changed'] = True + + if not self.i.m.check_mode: + return self._api_post({ + **self.i.call_cnf, + 'command': self.i.CMDS['add'], + 'data': self._get_request_data(), + }) + + def update(self, enable_switch: bool = True) -> dict: + self._set_existing() + sort_param_lists(self.i.p) + sort_param_lists(self.e) + + # checking if changed + for field in self.i.FIELDS_CHANGE: + if field in self.i.p: + if self.PARAM_MATCH_FIELDS in self.i.p: + if field in self.i.p[self.PARAM_MATCH_FIELDS]: + continue + + if hasattr(self.i, self.ATTR_FIELD_ID): + if field == getattr(self.i, self.ATTR_FIELD_ID): + continue + + try: + if self.i.p[field] is None: + self.i.p[field] = '' + + if str(self.e[field]) != str(self.i.p[field]): + self.i.r['changed'] = True + + if self.i.p['debug']: + self.i.m.warn( + f"Field changed: '{field}' " + f"'{self.e[field]}' != '{self.i.p[field]}'" + ) + + break + + except KeyError: + exit_bug( + f"The field '{field}' seems to be unset - check the modules config!" + ) + + # update if changed + if self.i.r['changed']: + if self.i.p['debug']: + self.i.m.warn(f"{self.i.r['diff']}") + + if not self.i.m.check_mode: + if hasattr(self.i, '_update_call'): + response = self.i._update_call() + + else: + response = self._api_post({ + **self.i.call_cnf, + 'command': self.i.CMDS['set'], + 'data': self._get_request_data(), + }) + + if self.i.p['debug']: + self.i.m.warn(f"{self.i.r['diff']}") + + return response + + elif enable_switch: + self._update_enabled() + + def _update_enabled(self) -> None: + existing = getattr(self.i, self.i.EXIST_ATTR) + + if 'enabled' in existing: + if existing['enabled'] != self.i.p['enabled']: + _bool_invert_fields = [] + enable = self.i.p['enabled'] + invert = False + + if hasattr(self.i, self.ATTR_BOOL_INVERT): + _bool_invert_fields = getattr(self.i, self.ATTR_BOOL_INVERT) + + if 'enabled' in _bool_invert_fields: + invert = True + enable = not enable + + if enable: + if hasattr(self.i, 'enable'): + self.i.enable() + + else: + self.enable(invert=invert) + + else: + if hasattr(self.i, 'disable'): + self.i.disable() + + else: + self.disable(invert=invert) + + def delete(self) -> dict: + self.i.r['changed'] = True + self.i.r['diff']['after'] = {} + + if not self.i.m.check_mode: + if hasattr(self.i, '_delete_call'): + response = self.i._delete_call() + + else: + response = self._api_post({ + **self.i.call_cnf, + 'command': self.i.CMDS['del'], + }) + + if self.i.p['debug']: + self.i.m.warn(f"{self.i.r['diff']}") + + return response + + def reload(self) -> dict: + # reload the running config + cont_rel = self.i.API_CONT + cmd_rel = 'reconfigure' + + if hasattr(self.i, self.ATTR_REL_CONT): + cont_rel = getattr(self.i, self.ATTR_REL_CONT) + + if hasattr(self.i, self.ATTR_REL_CMD): + cmd_rel = getattr(self.i, self.ATTR_REL_CMD) + + if not self.i.m.check_mode: + return self._api_post({ + 'module': self.i.API_MOD, + 'controller': cont_rel, + 'command': cmd_rel, + 'params': [] + }) + + def _get_request_data(self) -> dict: + if hasattr(self.i, '_build_request'): + return self.i._build_request() + + return self.build_request() + + def _change_enabled_state(self) -> dict: + return self._api_post({ + **self.i.call_cnf, + 'command': self.i.CMDS['toggle'], + 'params': [getattr(self.i, self.i.EXIST_ATTR)[self.field_pk]], + }) + + def is_enabled(self, invert: bool = False) -> bool: + is_enabled = getattr(self.i, self.i.EXIST_ATTR)['enabled'] + + if invert: + is_enabled = not is_enabled + + return is_enabled + + def enable(self, invert: bool = False) -> dict: + if self.i.exists and not self.is_enabled(invert=invert): + self.i.r['changed'] = True + if not invert: + self.i.r['diff']['before'] = {'enabled': False} + self.i.r['diff']['after'] = {'enabled': True} + + else: + self.i.r['diff']['before'] = {'enabled': True} + self.i.r['diff']['after'] = {'enabled': False} + + if not self.i.m.check_mode: + return self._change_enabled_state() + + def disable(self, invert: bool = False) -> dict: + if self.i.exists and self.is_enabled(invert=invert): + self.i.r['changed'] = True + if not invert: + self.i.r['diff']['before'] = {'enabled': True} + self.i.r['diff']['after'] = {'enabled': False} + + else: + self.i.r['diff']['before'] = {'enabled': False} + self.i.r['diff']['after'] = {'enabled': True} + + if not self.i.m.check_mode: + return self._change_enabled_state() + + def build_diff(self, data: dict) -> dict: + if not isinstance(data, dict): + exit_bug('The diff-source object must be of type dict!') + + _exclude_fields = [] + _no_log_fields = [] + + if hasattr(self.i, self.ATTR_DIFF_EXCL): + _exclude_fields = getattr(self.i, self.ATTR_DIFF_EXCL) + + if hasattr(self.i, self.ATTR_DIFF_NO_LOG): + _no_log_fields = getattr(self.i, self.ATTR_DIFF_NO_LOG) + + self._set_existing() + + diff = { + self.field_pk: self.e[self.field_pk] if self.field_pk in self.e else None + } + + for field in self.i.FIELDS_ALL: + if field in _exclude_fields: + continue + + if field in _no_log_fields: + diff[field] = self.VALUE_NO_LOG + continue + + stringify = True + + try: + diff[field] = data[field] + + except KeyError: + if field in self.i.p: + diff[field] = self.i.p[field] + + if isinstance(diff[field], list): + try: + diff[field].sort() + + except TypeError: + raise exit_bug(f"Field not defined as 'select_opt_list' type: {diff[field]}") + + stringify = False + + elif isinstance(diff[field], str) and diff[field].isnumeric: + try: + diff[field] = int(diff[field]) + stringify = False + + except (TypeError, ValueError): + pass + + elif isinstance(diff[field], dict) and self.field_pk in diff[field]: + diff[field] = diff[field][self.field_pk] + + elif isinstance(diff[field], (bool, int)): + stringify = False + + elif diff[field] is None: + diff[field] = '' + + if stringify: + try: + diff[field] = round(float(diff[field]), self.DIFF_FLOAT_ROUND) + stringify = False + + except (TypeError, ValueError): + pass + + if stringify: + diff[field] = str(diff[field]) + + return diff + + def build_request(self, ignore_fields: list = None) -> dict: + request = {} + _translate_fields = {} + _translate_values = {} + _bool_invert_fields = [] + + if ignore_fields is None: + ignore_fields = [] + + if is_unset(self.e): + self.e = getattr(self.i, self.i.EXIST_ATTR) + + if hasattr(self.i, self.ATTR_TRANSLATE): + _translate_fields = getattr(self.i, self.ATTR_TRANSLATE) + + if hasattr(self.i, self.ATTR_VALUE_MAP): + _translate_values = getattr(self.i, self.ATTR_VALUE_MAP) + + if hasattr(self.i, self.ATTR_BOOL_INVERT): + _bool_invert_fields = getattr(self.i, self.ATTR_BOOL_INVERT) + + for field in self.i.FIELDS_ALL: + if field in ignore_fields: + continue + + opn_field = field + if field in _translate_fields: + opn_field = _translate_fields[field] + + if field in self.i.p: + opn_data = self.i.p[field] + + elif field in self.e: + opn_data = self.e[field] + + else: + opn_data = '' + + if field in _translate_values: + try: + opn_data = _translate_values[field][opn_data] + + except KeyError: + pass + + if isinstance(opn_data, bool): + if field in _bool_invert_fields: + opn_data = not opn_data + + request[opn_field] = to_digit(opn_data) + + elif isinstance(opn_data, list): + join_char = self.RESP_JOIN_CHAR + + if hasattr(self.i, self.ATTR_JOIN_CHAR): + join_char = getattr(self.i, self.ATTR_JOIN_CHAR) + + request[opn_field] = join_char.join(opn_data) + + elif opn_data is None: + request[opn_field] = '' + + else: + request[opn_field] = opn_data + + if isinstance(opn_field, tuple): + hreqest = reduce(lambda r, i: r.setdefault(i, {}), opn_field[:-1], request) + hreqest[opn_field[-1]] = request.pop(opn_field) + + payload = request + + if hasattr(self.i, self.ATTR_AK_PATH_REQ): + ak_path = getattr(self.i, self.ATTR_AK_PATH_REQ).split(self.ATTR_AK_PATH_SPLIT_CHAR) + ak_path.reverse() + + for k in ak_path: + payload = {k: payload} + + elif hasattr(self.i, self.ATTR_AK_PATH): + # request only needs the last key + ak_path = getattr(self.i, self.ATTR_AK_PATH) + attr_ak = ak_path + + if ak_path.find('.') != -1: + attr_ak = ak_path.rsplit(self.ATTR_AK_PATH_SPLIT_CHAR, 1)[1] + + payload = {attr_ak: payload} + + return payload + + def find_single_link( + self, field: str, existing: dict, set_field: str = None, existing_field_id: str = 'name', + fail: bool = True + ) -> bool: + entry = None + + if not is_unset(self.i.p[field]): + found = False + if set_field is None: + set_field = field + + if len(existing) > 0: + for uuid, item in existing.items(): + if item[existing_field_id] == self.i.p[field]: + self.i.p[set_field] = uuid + entry = item[existing_field_id] + found = True + + if not found: + if fail: + if self.i.p['debug']: + self.i.m.warn(f"Unable to find link by field '{field}': '{self.i.p[field]}' in '{existing}'") + + self.i.m.fail_json( + f"Provided {field} '{self.i.p[field]}' was not found!" + ) + + return False + + if 'before' in self.i.r['diff'] and set_field in self.i.r['diff']['before']: + self.i.r['diff']['before'][set_field] = entry + + return True + + def find_multiple_links( + self, field: str, existing: dict, set_field: str = None, existing_field_id: str = 'name', + fail: bool = True, fail_soft: bool = False + ) -> bool: + provided = len(self.i.p[field]) > 0 + uuids = [] + entries = [] + + if not provided: + return True + + if existing is not None and len(existing) > 0: + for uuid, item in existing.items(): + if item[existing_field_id] in self.i.p[field]: + uuids.append(uuid) + entries.append(item[existing_field_id]) + + if len(uuids) == len(self.i.p[field]): + break + + if len(uuids) != len(self.i.p[field]): + msg = f"At least one of the provided {field} entries was not found!" + + if fail: + self.i.m.fail_json(msg) + + if fail_soft: + raise ModuleSoftError(msg) + + return False + + if set_field is None: + set_field = field + + self.i.p[set_field] = uuids + if set_field in self.i.r['diff']['before']: + entries.sort() + self.i.r['diff']['before'][set_field] = entries + + if set_field in self.i.r['diff']['after']: + self.i.r['diff']['after'][set_field].sort() + + return True + + def _set_existing(self) -> None: + if is_unset(self.e): + _existing = getattr(self.i, self.i.EXIST_ATTR) + + if _existing is not None and len(_existing) > 0: + self.e = _existing + + def simplify_existing(self, existing: dict) -> dict: + translate, typing, bool_invert, value_map = {}, {}, [], {} + + if hasattr(self.i, self.ATTR_TRANSLATE): + translate = getattr(self.i, self.ATTR_TRANSLATE) + + if hasattr(self.i, self.ATTR_TYPING): + typing = getattr(self.i, self.ATTR_TYPING) + + if hasattr(self.i, self.ATTR_BOOL_INVERT): + bool_invert = getattr(self.i, self.ATTR_BOOL_INVERT) + + if hasattr(self.i, self.ATTR_VALUE_MAP_RCV): + value_map = getattr(self.i, self.ATTR_VALUE_MAP_RCV) + + elif hasattr(self.i, self.ATTR_VALUE_MAP): + value_map = getattr(self.i, self.ATTR_VALUE_MAP) + + return simplify_translate( + existing=existing, + typing=typing, + translate=translate, + bool_invert=bool_invert, + value_map=value_map, + ) + + @property + def field_pk(self) -> str: + if hasattr(self.i, self.ATTR_FIELD_PK): + return getattr(self.i, self.ATTR_FIELD_PK) + + return 'uuid' + + def _call_simple(self) -> Callable: + if hasattr(self.i, 'simplify_existing'): + return self.i.simplify_existing + + if hasattr(self.i, '_simplify_existing'): + return self.i._simplify_existing + + return self.simplify_existing + + def _call_search(self, match_fields: list = None) -> (list, dict): + if hasattr(self.i, '_search_call'): + return self.i._search_call() + + return self.search(match_fields) + + def _api_headers(self) -> dict: + if hasattr(self.i, self.ATTR_HEADERS): + return getattr(self.i, self.ATTR_HEADERS) + + return {} + + def _api_post(self, cnf: dict) -> (dict, list): + if hasattr(self.i, 's'): + return self.i.s.post( + cnf=cnf, + headers=self._api_headers() + ) + + return single_post( + cnf=cnf, + module=self.i.m, + headers=self._api_headers() + ) + + def _api_get(self, cnf: dict) -> (dict, list): + if hasattr(self.i, 's'): + return self.i.s.get(cnf=cnf) + + return single_get( + cnf=cnf, + module=self.i.m + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/cls.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/cls.py new file mode 100644 index 0000000..7d383d3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/cls.py @@ -0,0 +1,143 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.base import Base +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + validate_int_fields, validate_str_fields + + +class BaseShared: + def __init__(self, m: AnsibleModule, r: dict, s: Session): + if hasattr(self, 'TIMEOUT'): + self.s = Session( + module=m, + timeout=self.TIMEOUT, + ) if s is None else s + + else: + self.s = Session(module=m) if s is None else s + + self.m = m + self.p = m.params + self.r = r + self.b = Base(instance=self) + self.exists = False + self.existing_entries = None + self.call_cnf = { + 'module': self.b.i.API_MOD, + 'controller': self.b.i.API_CONT, + } + + def _check_validators(self): + if 'state' in self.p and self.p['state'] != 'present': + return + + if hasattr(self.b.i, 'STR_VALIDATIONS'): + if hasattr(self.b.i, 'STR_LEN_VALIDATIONS'): + validate_str_fields( + module=self.m, + data=self.p, + field_regex=self.b.i.STR_VALIDATIONS, + field_minmax_length=self.b.i.STR_LEN_VALIDATIONS + ) + + else: + validate_str_fields(module=self.m, data=self.p, field_regex=self.b.i.STR_VALIDATIONS) + + elif hasattr(self.b.i, 'STR_LEN_VALIDATIONS'): + validate_str_fields(module=self.m, data=self.p, field_minmax_length=self.b.i.STR_LEN_VALIDATIONS) + + if hasattr(self.b.i, 'INT_VALIDATIONS'): + validate_int_fields(module=self.m, data=self.p, field_minmax=self.b.i.INT_VALIDATIONS) + + +class BaseModule(BaseShared): + def __init__(self, m: AnsibleModule, r: dict, s: Session = None, f: dict = None, multi: dict = None): + super().__init__(m, r, s) + if f is None: + f = {} + + if multi is not None and len(multi) > 0: + # override params by MultiModule + self.p = multi + + self.fail_verify = f.get('verify', False) + self.fail_process = f.get('process', False) + + def _base_check(self, match_fields: list = None): + self._check_validators() + + if match_fields is None: + if 'match_fields' in self.p: + match_fields = self.p['match_fields'] + + elif hasattr(self, 'FIELD_ID'): + match_fields = [self.FIELD_ID] + + if match_fields is not None: + self.b.find(match_fields=match_fields) + if self.exists: + self.call_cnf['params'] = [getattr(self, self.EXIST_ATTR)[self.b.field_pk]] + + if 'state' not in self.p or self.p['state'] == 'present': + self.r['diff']['after'] = self.b.build_diff(data=self.p) + + def check(self) -> None: + self._base_check() + + def get_existing(self) -> list: + return self.b.get_existing() + + def process(self) -> None: + self.b.process() + + def create(self) -> None: + self.b.create() + + def update(self) -> None: + self.b.update() + + def delete(self) -> None: + self.b.delete() + + def reload(self) -> None: + self.b.reload() + + +class GeneralModule(BaseShared): + # has only a single entry; cannot be deleted or created + EXIST_ATTR = 'settings' + + def __init__(self, m: AnsibleModule, r: dict, s: Session = None): + super().__init__(m, r, s) + self.settings = {} + + def _base_check(self): + self._check_validators() + self.settings = self._search_call() + self._build_diff() + + def check(self) -> None: + self._base_check() + + def _search_call(self) -> dict: + return self.b.simplify_existing(self.b.search()) + + def get_existing(self) -> dict: + return self._search_call() + + def process(self) -> None: + self.update() + + def update(self) -> None: + self.b.update(enable_switch=False) + + def reload(self) -> None: + self.b.reload() + + def _build_diff(self) -> None: + self.r['diff']['before'] = self.b.build_diff(self.settings) + self.r['diff']['after'] = self.b.build_diff({ + k: v for k, v in self.p.items() if k in self.settings + }) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/handler.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/handler.py new file mode 100644 index 0000000..38c6d70 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/handler.py @@ -0,0 +1,39 @@ +try: + from ansible.module_utils.common.errors import AnsibleModuleError + +except ModuleNotFoundError: + class AnsibleModuleError(Exception): + pass + +MODULE_EXCEPTIONS = (ModuleNotFoundError, ImportError) + + +class ModuleSoftError(Exception): + pass + + +class ModuleValidationError(ModuleSoftError): + pass + + +def exit_bug(msg: str): + raise AnsibleModuleError(f"THIS MIGHT BE A MODULE-BUG: {msg}") + + +def exit_debug(msg: str): + raise AnsibleModuleError(f"DEBUG INFO: {msg}") + + +def exit_env(msg: str): + raise AnsibleModuleError(f"ENVIRONMENTAL ERROR: {msg}") + + +def exit_cnf(msg: str): + raise AnsibleModuleError(f"CONFIG ERROR: {msg}") + + +def module_dependency_error() -> None: + exit_env( + 'For this Ansible-module to work you must install its dependencies first: ' + "'python3 -m pip install --upgrade httpx'" + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/multi.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/multi.py new file mode 100644 index 0000000..93437e8 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/multi.py @@ -0,0 +1,596 @@ +from abc import ABC + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.common.arg_spec import ModuleArgumentSpecValidator + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import ModuleSoftError +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import diff_remove_empty +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG_DEF_FALSE + + +def build_multi_mod_args( + mod_args: dict, + aliases: list = None, + description: str = None, + not_required: list[str] = None, +) -> dict: + """ + Function to dynamically build the module-arguments required for mass-management. + + :param mod_args: The module-specific arguments + :param aliases: List of module-specific aliases for the 'multi' argument + :param description: An optional description for the 'multi' argument + :param not_required: List of module-specific arguments (only keys) that should be set to be NOT required + :return: Module arguments required for mass-management + """ + args_base = mod_args.copy() + + aliases_purge = ['multi_delete', 'purge', 'many_purge'] + if aliases is None: + aliases = [] + + else: + for a in aliases: + aliases_purge.append(f'{a}_purge') + + aliases.append('many') + + if description is None: + description = 'Provide multiple entries to manage' + + for opn_arg in OPN_MOD_ARGS: + if opn_arg in args_base: + args_base.pop(opn_arg) + + if 'reload' in args_base: + args_base.pop('reload') + + opn_args_multi = OPN_MOD_ARGS.copy() + opn_args_multi['firewall']['required'] = False + + if not_required is None: + not_required = [] + + not_required.append('match_fields') + for field in not_required: + if field in args_base: + args_base[field]['required'] = False + + entry_args_full = {**args_base, **RELOAD_MOD_ARG_DEF_FALSE, **opn_args_multi} + + return dict( + multi=dict( + type='list', required=False, default=[], aliases=aliases, + description=description, + elements='dict', options=entry_args_full, + ), + multi_purge=dict( + type='list', required=False, default=[], aliases=aliases_purge, + description='Provide multiple entries to purge (delete or disable)', + elements='dict', options=entry_args_full, + ), + multi_control=dict( + type='dict', required=False, default={}, aliases=['multi_ctrl', 'mc'], + description=description, + options=dict( + # NOTE: we keep state and enabled outside overrides for convenience + state=dict(type='str', required=False, choices=['present', 'absent'], default=None), + enabled=dict(type='bool', required=False, default=None), + # overrides for other parameters + override=dict( + type='dict', required=False, default={}, description='Parameters to override for all entries', + aliases=['all', 'overrides'], + ), + fail_verify=dict( + type='bool', required=False, default=False, aliases=['fail_verification'], + description='Fail module if a single entry fails the verification.' + ), + fail_process=dict( + type='bool', required=False, default=True, aliases=['fail_proc', 'fail_processing'], + description='Fail module if a single entry fails to be processed.' + ), + output_info=dict(type='bool', required=False, default=False, aliases=['info']), + purge_action=dict( + type='str', required=False, default='delete', choices=['disable', 'delete'], + description='What action to perform on the entries matched by the purge' + ), + purge_filter=dict( + type='dict', required=False, default={}, aliases=['purge_filters'], + description='Field-value pairs to filter on - per example: {param1: test} ' + "- to only purge entries that have 'param1' set to 'test'. WARNING: Make sure to run a " + 'check-mode beforehand and manually verify the deletions!' + ), + purge_filter_invert=dict( + type='bool', required=False, default=False, + description='If true - it will purge all but the filtered ones. WARNING: Make sure to run a ' + 'check-mode beforehand and manually verify the deletions!' + ), + purge_filter_partial=dict( + type='bool', required=False, default=False, + description='If true - the filter will also match if it is just a partial value-match. WARNING: ' + 'Make sure to run a check-mode beforehand and manually verify the deletions!' + ), + purge_all=dict( + type='bool', required=False, default=False, + description='If set to true and neither entries, nor filters are provided - all entries will be ' + 'purged. WARNING: Make sure to run a check-mode beforehand and manually ' + 'verify the deletions!' + ), + purge_unconfigured=dict( + type='bool', required=False, default=False, aliases=['purge_unknown', 'purge_orphaned'], + description='Usable if configured entries are supplied - will delete all entries NOT matched with ' + 'the configured ones. WARNING: Make sure to run a check-mode beforehand and manually ' + 'verify the deletions!' + ), + ), + ), + ) + + +class MultiModuleCallbacks(ABC): + # pylint: disable=W0613 + @staticmethod + def build(entry: dict) -> dict: + """ + Callback to make modifications to a raw entry that was provided by the user, before it gets processed. + This happens after the overrides were applied. + + :param entry: The entry that can be modified + :return: The modified entry + """ + return entry + + @staticmethod + def validation(entry: dict) -> bool: + """ + Callback to validate a raw entry that was provided by the use. + This Validation extends the default Ansible-Module-Argument Validation. + It is called after the 'build' callback. + + :param entry: The entry that should be validated + :return: If the entry was valid + """ + return True + + @staticmethod + def get_existing(meta_entry: BaseModule) -> dict: + """ + Callback to pull the 'existing_entries' that will be written to the cache. + + :param meta_entry: A dummy/meta-entry as BaseModule-instance that + is used to pull all existing entries from the API + :return: Result that will be used as cache. The key 'main' is required to contain the primary entries processed + """ + return {'main': meta_entry.get_existing()} + + @staticmethod + def set_existing(entry: BaseModule, cache: dict): + """ + Callback to set the 'existing_entries' of an entry-instance that is about to be processed + + :param entry: The entry BaseModule-instance that might need 'existing_entries' to be set + :param cache: The full cache + :return: None + """ + entry.existing_entries = cache['main'] + + @staticmethod + def update_existing(entry: BaseModule, cache: dict) -> dict: + """ + Callback to update the cache after an entry was processed + + :param entry: The entry that was just processed + :param cache: The full cache + :return: The updated cache + """ + + # adding the config of the new entry to the cache (WITHOUT UUID!) + if not entry.exists and entry.p.get('state', 'present') == 'present': # was just created + entry_cnf = entry.p.copy() + for opn_arg in OPN_MOD_ARGS: + try: + entry_cnf.pop(opn_arg) + + except KeyError: + continue + + cache['main'].append(entry_cnf) + + return cache + + @staticmethod + def purge_exclude(entry: dict) -> bool: + """ + Callback to check if an entry should be excluded from purge/deletion. + This should only be used if there are built-in entries that should be protected. + + :param entry: The entry that should get purged/deleted + :return: If the entry should be excluded from being purged + """ + return False + + +# pylint: disable=R0902,R0913,R0915,R0917 +class MultiModule: + def __init__( + self, module: AnsibleModule, result: dict, entry_args: dict, kind: str, obj: BaseModule, + validation: bool = True, cache_existing: bool = True, + callbacks: MultiModuleCallbacks = None, + ): + self.m = module + self.p = module.params + self.mc = self.p['multi_control'] + self.r = result + self.o = obj + self.k = kind + + if hasattr(self.o, 'TIMEOUT'): + self.s = Session( + module=self.m, + timeout=getattr(self.o, 'TIMEOUT'), + ) + + else: + self.s = Session(module=self.m) + + self.meta_entry = self.o(module=self.m, session=self.s, result={}) + self.mod_entry_args = entry_args + self.validation = validation + self.cache = {} + self._cache_original = {} + self.cache_existing = cache_existing + self.callbacks: MultiModuleCallbacks = callbacks + if self.callbacks is None: + self.callbacks = MultiModuleCallbacks() + + self._init_match_fields() + self._init_cases() + + def _init_match_fields(self): + self.field_id = None + + if hasattr(self.o, 'FIELD_ID'): + self.field_id = getattr(self.o, 'FIELD_ID') + + self.match_fields = self.p.get('match_fields', [self.field_id]) + + if self.field_id is None: + fallback = None + if 'match_fields' in self.p and self.p['match_fields'] is not None: + if len(self.p['match_fields']) > 0 and not isinstance(self.p['match_fields'], list): + self.p['match_fields'] = [self.p['match_fields']] + + fallback = self.p['match_fields'][0] + + self.field_id = getattr(self.o, 'MULTI_DIFF_KEY', fallback) + + def _init_cases(self): + self._has_multi_crud_entries = len(self.p['multi']) > 0 + self._has_multi_purge_entries = len(self.p['multi_purge']) > 0 + self._has_multi_purge_filters = len(self.mc['purge_filter']) > 0 + + def _is_multi_purge(self) -> bool: + return self._has_multi_purge_entries or self.mc['purge_all'] or self._has_multi_purge_filters + + def _is_multi_crud(self) -> bool: + return self._has_multi_crud_entries + + def process(self) -> None: + if self.cache_existing: + self.cache = self.callbacks.get_existing(self.meta_entry) + self._cache_original = self.cache.copy() + + if self._is_multi_purge(): + self._purge() + + elif self._is_multi_crud(): + self._create_update() + self._purge_unconfigured() + + else: + self.m.fail_json('Got invalid Mass-Management arguments!') + + if self.r['changed'] and self.p['reload']: + self.meta_entry.reload() + + self.s.close() + + # CREATE/UPDATE METHODS + def _validate_entry(self, entry: dict) -> bool: + result = False + if self.mc['fail_verify']: + error_func = self.m.fail_json + + else: + error_func = self.m.warn + + validation = ModuleArgumentSpecValidator(self.mod_entry_args).validate(parameters=entry) + + try: + validation_error = validation.errors[0] + + except IndexError: + validation_error = None + + if validation_error: + error_func(f"Got invalid config for {self.k} '{self._entry_id(entry)}': {validation_error}") + + else: + result = True + + if result: + try: + result = self.callbacks.validation(entry) + + except ModuleSoftError as e: + result = False + error_func(e) + + return result + + def _build_entries(self) -> list: + overrides = { + 'reload': False, + **self.mc['override'], # user-defined overrides + } + if 'match_fields' in self.p: + overrides['match_fields'] = self.p['match_fields'] + + if self.mc['state'] is not None: + overrides['state'] = self.mc['state'] + + if self.mc['enabled'] is not None: + overrides['enabled'] = self.mc['enabled'] + + # build list of valid entries or fail if invalid config is not permitted + valid_entries = [] + for entry_cnf in self.p['multi']: + # apply overrides + entry = { + **entry_cnf, + **overrides, + } + + for f in ['debug', 'profiling']: + if f not in entry: + entry[f] = self.p[f] + + entry = self.callbacks.build(entry) + + if not self.validation: + valid_entries.append(entry) + + else: + # (re-)validate the entry like ansible does on module-init + if entry['debug']: + self.m.warn(f"Validating {self.k}: '{entry}'") + + if self._validate_entry(entry): + valid_entries.append(entry) + + return valid_entries + + def _create_update(self): + if not self._has_multi_crud_entries: + return + + for entry_cnf in self._build_entries(): + # process single entry like in the single-module + entry_result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + self.p['debug'] = entry_cnf['debug'] # per entry switch + + if self.p['debug'] or self.mc['output_info']: + self.m.warn(f"Processing {self.k}: '{self._entry_id(entry_cnf)}'") + + try: + entry = self._init_entry(entry_cnf, entry_result) + try: + entry.check() + + except KeyError as e: + if str(e) == "KeyError: 'uuid'": + raise ModuleSoftError("Cannot modify entry that was just created!") + + entry.process() + + if self.cache_existing: + self.cache = self.callbacks.update_existing(entry, cache=self.cache) + + self._add_entry_result(entry, entry_result) + + except ModuleSoftError: + continue + + # PURGE METHODS + def _purge_entry(self, entry_cnf: dict) -> None: + entry_name = self._entry_id(entry_cnf) + + try: + entry_result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + entry = self._init_entry(entry_cnf, entry_result) + + if not entry.p.get('debug', False): + entry.p['debug'] = self.p['debug'] + + entry.p['state'] = 'absent' if self.mc['purge_action'] == 'delete' else 'present' + entry_cnf['match_fields'] = self.match_fields + + entry.check() + if not entry.exists: + return + + if self.mc['purge_action'] == 'delete': + entry_result['changed'] = True + self.r['diff']['before'][entry_name] = entry_cnf + self.r['diff']['after'][entry_name] = None + if not self.m.check_mode: + entry.delete() + + elif entry.b.is_enabled(): + entry_result['changed'] = True + self.r['diff']['before'][entry_name] = {'enabled': True} + self.r['diff']['after'][entry_name] = {'enabled': False} + if not self.m.check_mode: + entry.b.disable() + + self._add_entry_result(entry, entry_result) + + except ModuleSoftError: + pass + + def _in_entries_to_purge(self, entry_cnf: dict) -> bool: + for purge_entry_cnf in self.p['multi_purge']: + if self._entry_matches(entry_cnf, purge_entry_cnf): + return True + + return False + + def _in_entries_configured(self, entry_cnf: dict) -> bool: + for configured_entry_cnf in self.p['multi']: + if self._entry_matches(entry_cnf, configured_entry_cnf): + return True + + return False + + def _matches_purge_filter_partial(self, entry_cnf: dict) -> bool: + matches = [] + for k, v in self.mc['purge_filter'].items(): + matches.append(str(entry_cnf[k]).find(str(v)) != -1) + + return all(matches) + + def _matches_purge_filter_full(self, entry_cnf: dict) -> bool: + matches = [] + for k, v in self.mc['purge_filter'].items(): + matches.append(str(entry_cnf[k]) == str(v)) + + return all(matches) + + def _matches_purge_filter(self, entry_cnf: dict) -> bool: + # include in purge if matching & 'not inverted' - else exclude + if self.mc['purge_filter_partial']: + result = self._matches_purge_filter_partial(entry_cnf) + + else: + result = self._matches_purge_filter_full(entry_cnf) + + if self.mc['purge_filter_invert']: + return not result + + return result + + def _purge(self): + if not self.mc['purge_all'] and not self._has_multi_purge_entries and not self._has_multi_purge_filters: + self.m.fail_json("You need to either provide entries via 'multi_purge' or 'multi_control.purge_filter'!") + + # checking all existing entries if they should be deleted + for entry_cnf in self.cache['main']: + if self.callbacks.purge_exclude(entry_cnf): + continue + + # user chose to purge_all, use a purge_filter or has provided multi_purge-entries that contain this entry + purge_filter_all = self._has_multi_purge_filters and not self._has_multi_purge_entries + if self.mc['purge_all'] or purge_filter_all or self._in_entries_to_purge(entry_cnf): + # skip if purge_filters were provided, but do not match this entry + if self._has_multi_purge_filters and not self._matches_purge_filter(entry_cnf): + continue + + if self.p['debug']: + self.m.warn(f"Existing {self.k} '{self._entry_id(entry_cnf)}' will be {self.mc['purge_action']}d!") + + self._purge_entry(entry_cnf) + + def _purge_unconfigured(self): + if not self.mc['purge_unconfigured']: + return + + # checking all pre-existing entries if they are contained in the user's config or should be deleted + for entry_cnf in self._cache_original['main']: + if self.callbacks.purge_exclude(entry_cnf) or self._in_entries_configured(entry_cnf): + continue + + if self.p['debug']: + self.m.warn(f"Existing {self.k} '{self._entry_id(entry_cnf)}' will be {self.mc['purge_action']}d!") + + self._purge_entry(entry_cnf) + + # UTIL METHODS + def _init_entry(self, entry_cnf: dict, entry_result: dict) -> BaseModule: + entry_cnf['match_fields'] = self.match_fields + o = self.o( + module=self.m, + result=entry_result, + multi=entry_cnf, + session=self.s, + fail=dict( + verify=self.mc['fail_verify'], + process=self.mc['fail_process'], + ), + ) + if self.cache_existing: + self.callbacks.set_existing(o, cache=self.cache) + + return o + + def _entry_in_result(self, entry: (dict, BaseModule), before_after: str) -> bool: + return self._entry_id(entry) in self.r['diff'][before_after] + + def _add_entry_result(self, entry: BaseModule, entry_result: dict): + if entry_result['changed']: + self.r['changed'] = True + + entry_result['diff'] = diff_remove_empty(entry_result['diff'], to_none=True) + entry_name = self._entry_id(entry) + + if 'before' in entry_result['diff']: + self.r['diff']['before'][entry_name] = entry_result['diff']['before'] + + if 'after' in entry_result['diff']: + self.r['diff']['after'][entry_name] = entry_result['diff']['after'] + + def _purge_unconfigured_clean_diff(self, entry: (dict, BaseModule)): + # basically - nothing happened to this entry; so omit it from the diff + entry_name = self._entry_id(entry) + if entry_name in self.r['diff']['before'] and entry_name not in self.r['diff']['after']: + self.r['diff']['before'].pop(entry_name) + + def _entry_matches(self, e1: dict, e2: dict) -> bool: + matches = [] + for field in self.match_fields: + matches.append( + field in e1 and + field in e2 and + str(e1[field]) == str(e2[field]) + ) + + res = all(matches) + if not res and self.p['debug']: + self.m.warn(f"Entries do not match: {e1} != {e2}") + + return res + + def _entry_id(self, entry: (dict, BaseModule)) -> str: + entry_cnf = entry + if isinstance(entry, BaseModule): + entry_cnf = entry.p + + if self.field_id in entry_cnf: + return entry_cnf[self.field_id] + + return 'NO-ID-FOUND' diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/wrapper.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/wrapper.py new file mode 100644 index 0000000..c269a7a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/base/wrapper.py @@ -0,0 +1,53 @@ +from inspect import stack as inspect_stack +from inspect import getfile as inspect_getfile + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.multi import \ + MultiModule, MultiModuleCallbacks +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.utils import profiler +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import diff_remove_empty + + +def _single_module_process(instance: BaseModule): + instance.check() + instance.process() + if 'reload' in instance.m.params and instance.r['changed'] and instance.m.params['reload']: + instance.reload() + + if hasattr(instance, 's'): + instance.s.close() + + instance.r['diff'] = diff_remove_empty(instance.r['diff']) + + +def module_multi_wrapper( + module: AnsibleModule, result: dict, obj: BaseModule, kind: str, entry_args: dict, + callbacks: MultiModuleCallbacks = None, +): + m = MultiModule( + module=module, + result=result, + kind=kind, + obj=obj, + entry_args=entry_args['multi']['options'], + callbacks=callbacks, + ) + if module.params['profiling'] or module.params['debug']: + module_name = inspect_getfile(inspect_stack()[1][0]).rsplit('/', 1)[1].rsplit('.', 1)[0] + return profiler(check=m.process, module_name=module_name, kwargs={}) + + # if the user wants to purge every entry - it makes no sense to hinder it for a single one + if module.params['multi_control']['purge_all']: + module.params['multi_control']['fail_process'] = False + + return m.process() + + +def module_wrapper(instance: BaseModule): + if instance.m.params['profiling'] or instance.m.params['debug']: + module_name = inspect_getfile(inspect_stack()[1][0]).rsplit('/', 1)[1].rsplit('.', 1)[0] + return profiler(check=_single_module_process, module_name=module_name, kwargs={'instance': instance}) + + return _single_module_process(instance) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/alias.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/alias.py new file mode 100644 index 0000000..17cf1e0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/alias.py @@ -0,0 +1,31 @@ +from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + STATE_MOD_ARG + + +ALIAS_MOD_ARGS = dict( + name=dict(type='str', required=True, aliases=['n']), + description=dict( + type='str', required=False, default='', aliases=['desc'], + ), + content=dict( + type='list', required=False, default=[], aliases=['c', 'cont'], elements='str', + ), + type=dict(type='str', required=False, default='host', aliases=['t'], choices=[ + 'host', 'network', 'port', 'url', 'urltable', 'geoip', 'networkgroup', + 'mac', 'dynipv6host', 'internal', 'external', + ]), + updatefreq_days=dict( + type='str', default='', required=False, + description="Update frequency used by type 'urltable' in days - per example '0.5' for 12 hours" + ), + interface=dict( + type='str', default=None, aliases=['int', 'if'], required=False, + description=' Select the interface for the V6 dynamic IP.', + ), + path_expression=dict( + type='str', default='', aliases=['pe', 'jq'], required=False, + description='Simplified expression to select a field inside a container, a dot is used as field separator. ' + 'Expressions using the jq language are also supported.', + ), + **STATE_MOD_ARG, +) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/bind.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/bind.py new file mode 100644 index 0000000..0da076b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/bind.py @@ -0,0 +1,34 @@ +from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + STATE_MOD_ARG + + +BIND_REC_MATCH_FIELDS = dict( + match_fields=dict( + type='list', elements='str', + description='Fields that are used to match configured records with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['name', 'domain', 'type', 'value'], + default=['name', 'domain', 'type'], # required=False + ) +) +BIND_REC_MOD_ARGS = dict( + domain=dict(type='str', required=True, aliases=['domain_name']), + name=dict(type='str', required=True, aliases=['record']), + type=dict( + type='str', required=False, default='A', + choices=[ + 'A', 'AAAA', 'CAA', 'CNAME', 'DNSKEY', 'DS', 'MX', 'NS', 'PTR', + 'RRSIG', 'SRV', 'TLSA', 'TXT', + ] + ), + value=dict(type='str', required=False), + round_robin=dict( + type='bool', required=False, default=False, + description='If multiple records with the same domain/name/type combination exist - ' + "the module will only execute 'state=absent' if set to 'false'. " + "To create multiple ones set this to 'true'. " + "Records will only be created, NOT UPDATED! (no matching is done)" + ), + **BIND_REC_MATCH_FIELDS, + **STATE_MOD_ARG, +) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/ipsec_auth.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/ipsec_auth.py new file mode 100644 index 0000000..d0ddd0a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/ipsec_auth.py @@ -0,0 +1,40 @@ +IPSEC_AUTH_MOD_ARGS = dict( + name=dict( + type='str', required=True, aliases=['description', 'desc'], + description='Unique name to identify the entry' + ), + connection=dict( + type='str', required=False, aliases=['tunnel', 'conn', 'tun'], + description='Connection to use this local authentication with' + ), + round=dict( + type='int', required=False, default=0, + description='Numeric identifier by which authentication rounds are sorted' + ), + authentication=dict( + type='str', required=False, aliases=['auth'], default='psk', + choices=['psk', 'pubkey', 'eap-tls', 'eap-mschapv2', 'xauth-pam', 'eap-radius'], + description='Authentication to perform for this round, when using Pre-Shared key make sure to define one ' + 'under "VPN->IPsec->Pre-Shared Keys"', + ), + id=dict( + type='str', required=False, aliases=['ike_id'], default='', + description='IKE identity to use for authentication round. When using certificate authentication. The IKE ' + 'identity must be contained in the certificate, either as the subject DN or as a subjectAltName ' + '(the identity will default to the certificate’s subject DN if not specified). ' + 'Refer to https://docs.strongswan.org/docs/5.9/config/identityParsing.html for details on ' + 'how identities are parsed and may be configured' + ), + eap_id=dict( + type='str', required=False, default='', + description='Client EAP-Identity to use in EAP-Identity exchange and the EAP method' + ), + certificates=dict( + type='list', elements='str', required=False, aliases=['certs'], default=[], + description='List of certificate candidates to use for authentication' + ), + public_keys=dict( + type='list', elements='str', required=False, aliases=['pubkeys'], default=[], + description='List of raw public key candidates to use for authentication' + ), +) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/legacy_multi.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/legacy_multi.py new file mode 100644 index 0000000..12a44b3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/legacy_multi.py @@ -0,0 +1,50 @@ +PURGE_MOD_ARGS = dict( + action=dict( + type='str', required=False, default='delete', choices=['disable', 'delete'], + description='What to do with the matched items' + ), + filters=dict( + type='dict', required=False, default={}, + description='Field-value pairs to filter on - per example: {param1: test} ' + "- to only purge items that have 'param1' set to 'test'" + ), + filter_invert=dict( + type='bool', required=False, default=False, + description='If true - it will purge all but the filtered ones' + ), + filter_partial=dict( + type='bool', required=False, default=False, + description="If true - the filter will also match if it is just a partial value-match" + ), + force_all=dict( + type='bool', required=False, default=False, + description='If set to true and neither items, nor filters are provided - all items will be purged' + ), +) + +STATE_MOD_ARG_MULTI = dict( + state=dict(type='str', required=False, choices=['present', 'absent']), + enabled=dict(type='bool', required=False, default=None), # override only if set +) + +FAIL_MOD_ARG_MULTI = dict( + fail_verification=dict( + type='bool', required=False, default=False, aliases=['fail_verify'], + description='Fail module if a single entry fails the verification.' + ), + fail_processing=dict( + type='bool', required=False, default=True, aliases=['fail_proc'], + description='Fail module if a single entry fails to be processed.' + ), +) + +INFO_MOD_ARG = dict( + output_info=dict(type='bool', required=False, default=False, aliases=['info']), +) + +RULE_MOD_ARG_KEY_FIELD = dict( + key_field=dict( + type='str', required=True, choices=['sequence', 'description', 'uuid'], aliases=['key'], + description='What field is used as key of the provided dictionary' + ) +) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/main.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/main.py new file mode 100644 index 0000000..923fc40 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/main.py @@ -0,0 +1,95 @@ +OPN_MOD_ARGS = dict( + firewall=dict( + type='str', required=True, + description="IP-Address or DNS hostname of the target firewall. " + "Must be included as 'common name' or 'subject alternative name' in the firewalls web-certificate " + "to use 'ssl_verify=true'" + ), + api_port=dict( + type='int', required=False, default=443, + description='Port the target firewall uses for its web-interface' + ), + api_key=dict( + type='str', required=False, no_log=True, + description="API key used to authenticate, alternative to 'api_credential_file'" + ), + api_secret=dict( + type='str', required=False, no_log=True, + description="API secret used to authenticate, alternative to 'api_credential_file'. " + "Is set as 'no_log' parameter" + ), + api_credential_file=dict( + type='path', required=False, + description="Path to the api-credential file as downloaded through the web-interface. " + "Alternative to 'api_key' and 'api_secret'" + ), + ssl_verify=dict( + type='bool', required=False, default=True, + description='If the certificate of the target firewall should be validated. RECOMMENDED FOR PRODUCTION USAGE!' + ), + ssl_ca_file=dict( + type='path', required=False, + description='If you use an internal certificate-authority to create the certificate of the target firewall, ' + 'provide the path to its public key for validation' + ), + debug=dict( + type='bool', required=False, default=False, + description="Used to en-/disable the debug mode. All API requests and responses will be shown " + "as Ansible warnings at runtime. Will be hidden if the tasks 'no_log' parameter is set to 'true'" + ), + profiling=dict( + type='bool', required=False, default=False, + description="Used to en-/disable the profiling mode. " + "Time consumption of the module will be logged to '/tmp/oxlorg.opnsense'" + ), + api_timeout=dict( + type='float', required=False, aliases=['timeout'], + description='Manually override the modules default API-request timeout' + ), + api_retries=dict( + type='int', required=False, default=0, aliases=['connect_retries'], + description='Number of retries on API requests, in case there is an error when establishing the connection. ' + 'This does not handle errors returned by the OPNsense system' + ), +) + +BUILTIN_ALIASES = [ + 'bogons', 'bogonsv6', 'sshlockout', 'virusprot', '__wazuh_agent_drop', +] +BUILTIN_INTERFACE_ALIASES_REG = '^__.*?_network$' # auto-added interface aliases + +STATE_ONLY_MOD_ARG = dict( + state=dict(type='str', required=False, choices=['present', 'absent'], default='present'), +) + +EN_ONLY_MOD_ARG = dict( + enabled=dict(type='bool', required=False, default=True), +) + +STATE_MOD_ARG = dict( + **STATE_ONLY_MOD_ARG, + **EN_ONLY_MOD_ARG, +) + +RELOAD_MOD_ARG = dict( + reload=dict( + type='bool', required=False, default=True, aliases=['apply'], + description='If the running config should be reloaded/applied on change - ' + 'will take some time' + ) +) + +RELOAD_MOD_ARG_DEF_FALSE = dict( + reload=dict( + type='bool', required=False, default=False, aliases=['apply'], + description='If the running config should be reloaded on change - ' + 'will take some time' + ) +) + +DEBUG_CONFIG = dict( + path_log='/tmp/oxlorg.opnsense', + log_api_calls='api_calls.log', +) + +CONNECTION_TEST_TIMEOUT = 1.5 diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/openvpn.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/openvpn.py new file mode 100644 index 0000000..5141390 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/openvpn.py @@ -0,0 +1,110 @@ +OPENVPN_INSTANCE_MOD_ARGS = dict( + name=dict( + type='str', required=True, aliases=['desc', 'description'], + description='The name used to match this config to existing entries' + ), + protocol=dict( + type='str', required=False, default='udp', aliases=['proto'], + choices=['udp', 'udp4', 'udp6', 'tcp', 'tcp4', 'tcp6'], + description='Use protocol for communicating with remote host.' + ), + address=dict( + type='str', required=False, default='', aliases=['bind_address', 'ip', 'bind'], + description='Optional IP address for bind.' + 'If specified, OpenVPN will bind to this address only.' + 'If unspecified, OpenVPN will bind to all interfaces.' + ), + mode=dict( + type='str', required=False, default='tun', aliases=['type'], choices=['tun', 'tap'], + description='Choose the type of tunnel, OSI Layer 3 [tun] is the most common option ' + 'to route IPv4 or IPv6 traffic, [tap] offers Ethernet 802.3 (OSI Layer 2) connectivity ' + 'between hosts and is usually combined with a bridge.' + ), + log_level=dict( + type='int', required=False, default=3, aliases=['verbosity', 'verb'], + choices=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + description='Output verbosity level. 0 = no output, 1-4 = normal, 5 = log packets, 6-11 debug' + ), + keepalive_interval=dict( + type='str', required=False, default='', aliases=['kai'], + description='Ping interval in seconds. 0 to disable keep alive' + ), + keepalive_timeout=dict( + type='str', required=False, default='', aliases=['kat'], + description='Causes OpenVPN to restart after n seconds pass without reception of a ' + 'ping or other packet from remote.' + ), + # trust + certificate=dict( + type='str', required=False, aliases=['cert'], + description='Certificate to use for this service.' + ), + verify_remote_certificate=dict( + type='bool', required=False, default=False, + description='Require that the peer certificate was signed with an explicit "key usage" and ' + '"extended key usage" based on RFC 3280 rules.' + ), + ca=dict( + type='str', required=False, default='', aliases=['certificate_authority', 'authority'], + description='Select a certificate authority when it differs from the attached certificate.' + ), + key=dict( + type='str', required=False, default='', aliases=['tls_key', 'tls_static_key'], + description='Add an additional layer of HMAC authentication on top of the TLS control channel to ' + 'mitigate DoS attacks and attacks on the TLS stack. The prefixed mode determines if ' + 'this measurement is only used for authentication (--tls-auth) or includes encryption ' + '(--tls-crypt).' + ), + authentication=dict( + type='str', required=False, default='', aliases=['auth', 'auth_algo'], + choices=[ + '', 'BLAKE2b512', 'BLAKE2s256', 'whirlpool', 'none', + 'MD4', 'MD5', 'MD5-SHA1', 'RIPEMD160', 'SHA1', 'SHA224', 'SHA256', 'SHA3-224', 'SHA3-256', + 'SHA3-384', 'SHA3-512', 'SHA384', 'SHA512', 'SHA512-224', 'SHA512-256', 'SHAKE128', 'SHAKE256', + ], + description='Authenticate data channel packets and (if enabled) tls-auth control channel packets with ' + 'HMAC using message digest algorithm alg.' + ), + # auth + renegotiate_time=dict( + type='str', required=False, default='', aliases=['reneg_time', 'reneg'], + description='Renegotiate data channel key after n seconds (default=3600). When using a one time ' + 'password, be advised that your connection will automatically drop because your ' + 'password is not valid anymore. Set to 0 to disable, remember to change your ' + 'client as well.' + ), + # routing + network_local=dict( + type='list', elements='str', required=False, default=[], aliases=['net_local', 'push_route'], + description='These are the networks accessible on this host, these are pushed via route{-ipv6} ' + 'clauses in OpenVPN to the client.' + ), + network_remote=dict( + type='list', elements='str', required=False, default=[], aliases=['net_remote', 'route'], + description='Remote networks for the server, add route to routing table after connection is established' + ), + # misc + options=dict( + type='list', elements='str', required=False, default=[], aliases=['opts'], + description='Various less frequently used yes/no options which can be set for this instance.', + choices=[ + 'client-to-client', 'duplicate-cn', 'passtos', 'persist-remote-ip', 'route-nopull', 'route-noexec', + 'remote-random', 'float', + ], + ), + mtu=dict( + type='str', required=False, default='', aliases=['tun_mtu'], + description='Take the TUN device MTU to be tun-mtu and derive the link MTU from it.' + ), + fragment_size=dict( + type='str', required=False, default='', aliases=['frag_size'], + description='Enable internal datagram fragmentation so that no UDP datagrams are sent which are larger ' + 'than the specified byte size.' + ), + mss_fix=dict( + type='bool', required=False, default=False, aliases=['mss'], + description='Announce to TCP sessions running over the tunnel that they should limit their send packet ' + 'sizes such that after OpenVPN has encapsulated them, the resulting UDP packet size that ' + 'OpenVPN sends to its peer will not exceed the recommended size.' + ), +) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/rule.py new file mode 100644 index 0000000..73bb71d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/defaults/rule.py @@ -0,0 +1,257 @@ +from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import STATE_MOD_ARG + +RULE_DEFAULTS = { + 'sequence': 1, + 'action': 'pass', + 'quick': True, + 'interface': ['lan'], + 'interface_invert': False, + 'direction': 'in', + 'ip_protocol': 'inet', + 'protocol': 'any', + 'source_invert': False, + 'source_net': 'any', + 'source_port': '', + 'destination_invert': False, + 'destination_net': 'any', + 'destination_port': '', + 'gateway': '', + 'replyto': '', + 'disable_replyto': False, + 'log': True, + 'allow_opts': False, + 'state_type': 'keep', + 'state_policy': '', + 'state_timeout': None, + 'max_states': None, + 'max_src_nodes': None, + 'max_src_states': None, + 'max_src_conn': None, + 'max_src_conn_rate': None, + 'max_src_conn_rates': None, + 'overload': None, + 'adaptive_start': None, + 'adaptive_end': None, + 'prio': '', + 'set_prio': '', + 'set_prio_low': '', + 'tag': '', + 'tagged': '', + 'tcp_flags': [], + 'tcp_flags_clear': [], + 'schedule': '', + 'tos': '', + 'state': 'present', + 'enabled': True, + 'description': '', + 'debug': False, + 'icmp_type': [], +} + +RULE_MOD_ARG_ALIASES = { + 'sequence': ['seq'], + 'action': ['a'], + 'quick': ['q'], + 'interface': ['int', 'i'], + 'interface_invert': ['int_inv', 'ii', 'int_not'], + 'direction': ['dir'], + 'ip_protocol': ['ip', 'ip_proto'], + 'protocol': ['proto', 'p'], + 'source_invert': ['src_inv', 'si', 'src_not'], + 'source_net': ['source', 'src', 's'], + 'source_port': ['src_port', 'sp'], + 'destination_invert': ['dest_inv', 'di', 'dest_not'], + 'destination_net': ['destination', 'dest', 'd'], + 'destination_port': ['dest_port', 'dp'], + 'gateway': ['gw', 'g'], + 'replyto': ['rt'], + 'log': ['l'], + 'allow_opts': ['opts'], + 'overload': ['ol'], + 'schedule': ['sched'], + 'description': ['name', 'desc'], + 'state': ['st'], + 'enabled': ['en'], + 'icmp_type': ['icmp_types'], +} + +RULE_MATCH_FIELDS_ARG = dict( + match_fields=dict( + type='list', required=True, elements='str', + description='Fields that are used to match configured rules with the running config - ' + "if any of those fields are changed, the module will think it's a new rule", + choices=[ + 'sequence', 'action', 'interface', 'direction', 'ip_protocol', 'protocol', + 'source_invert', 'source_net', 'source_port', 'destination_invert', 'destination_net', + 'destination_port', 'gateway', 'description', 'uuid', + ] + ), +) + +RULE_MOD_ARGS = dict( + sequence=dict( + type='int', required=False, default=RULE_DEFAULTS['sequence'], + aliases=RULE_MOD_ARG_ALIASES['sequence'] + ), + action=dict( + type='str', required=False, default=RULE_DEFAULTS['action'], choices=['pass', 'block', 'reject'], + aliases=RULE_MOD_ARG_ALIASES['action'] + ), + quick=dict(type='bool', required=False, default=RULE_DEFAULTS['quick'], aliases=RULE_MOD_ARG_ALIASES['quick']), + interface=dict( + type='list', required=False, default=RULE_DEFAULTS['interface'], aliases=RULE_MOD_ARG_ALIASES['interface'], + description='One or multiple interfaces use this rule on', elements='str', + ), + interface_invert=dict( + type='bool', required=False, default=RULE_DEFAULTS['interface_invert'], + aliases=RULE_MOD_ARG_ALIASES['interface_invert'], + ), + direction=dict( + type='str', required=False, default=RULE_DEFAULTS['direction'], aliases=RULE_MOD_ARG_ALIASES['direction'], + choices=['in', 'out'] + ), + ip_protocol=dict( + type='str', required=False, choices=['inet', 'inet6', 'inet46'], + default=RULE_DEFAULTS['ip_protocol'], description="IPv4 = 'inet', IPv6 = 'inet6', 'IPv4+6 = 'inet46'", + aliases=RULE_MOD_ARG_ALIASES['ip_protocol'], + ), + protocol=dict( + type='str', required=False, default=RULE_DEFAULTS['protocol'], aliases=RULE_MOD_ARG_ALIASES['protocol'], + description="Protocol like 'TCP', 'UDP', 'ICMP', 'TCP/UDP' and so on." + ), + source_invert=dict( + type='bool', required=False, default=RULE_DEFAULTS['source_invert'], + aliases=RULE_MOD_ARG_ALIASES['source_invert'], + ), + source_net=dict( + type='str', required=False, default=RULE_DEFAULTS['source_net'], aliases=RULE_MOD_ARG_ALIASES['source_net'], + description="Host, network, alias or 'any'", + ), + source_port=dict( + type='str', required=False, default=RULE_DEFAULTS['source_port'], aliases=RULE_MOD_ARG_ALIASES['source_port'], + description='Leave empty to allow all, valid port-number, name, alias or range' + ), + destination_invert=dict( + type='bool', required=False, default=RULE_DEFAULTS['destination_invert'], + aliases=RULE_MOD_ARG_ALIASES['destination_invert'], + ), + destination_net=dict( + type='str', required=False, default=RULE_DEFAULTS['destination_net'], + aliases=RULE_MOD_ARG_ALIASES['destination_net'], description="Host, network, alias or 'any'" + ), + destination_port=dict( + type='str', required=False, default=RULE_DEFAULTS['destination_port'], + aliases=RULE_MOD_ARG_ALIASES['destination_port'], + description='Leave empty to allow all, valid port-number, name, alias or range' + ), + gateway=dict( + type='str', required=False, default=RULE_DEFAULTS['gateway'], + aliases=RULE_MOD_ARG_ALIASES['gateway'], description='Existing gateway to use' + ), + replyto=dict( + type='str', required=False, default=RULE_DEFAULTS['replyto'], + aliases=RULE_MOD_ARG_ALIASES['replyto'], + description='Determines how packets route back in the opposite direction' + ), + disable_replyto=dict( + type='bool', required=False, default=RULE_DEFAULTS['disable_replyto'], + description='Explicit disable reply-to for this rule' + ), + log=dict(type='bool', required=False, default=RULE_DEFAULTS['log'], aliases=RULE_MOD_ARG_ALIASES['log'],), + allow_opts=dict( + type='bool', required=False, default=RULE_DEFAULTS['allow_opts'], aliases=RULE_MOD_ARG_ALIASES['allow_opts'], + description='Allows packets with IP options to pass' + ), + state_type=dict( + type='str', required=False, default=RULE_DEFAULTS['state_type'], + choices=['keep', 'sloppy', 'modulate', 'synproxy', 'none'], + description='State tracking mechanism to use' + ), + state_policy=dict( + type='str', required=False, default=RULE_DEFAULTS['state_policy'], + choices=['', 'if-bound', 'floating'], description='State tracking mechanism to use' + ), + state_timeout=dict( + type='int', required=False, + description='State Timeout in seconds (TCP only)' + ), + max_states=dict(type='int', required=False, description='Limits the number of concurrent states',), + max_src_nodes=dict( + type='int', required=False, + description='Limits the number of source addresses which can simultaneously have state table entries' + ), + max_src_states=dict( + type='int', required=False, + description='Limits the number of simultaneous state entries that a single source address can create' + ), + max_src_conn=dict( + type='int', required=False, + description='Limit the number of simultaneous TCP connections a single host can make' + ), + max_src_conn_rate=dict( + type='int', required=False, + description='Maximum new connections per host, measured over time' + ), + max_src_conn_rates=dict( + type='int', required=False, + description='Time interval (seconds) to measure the number of connections' + ), + overload=dict( + type='str', required=False, + aliases=RULE_MOD_ARG_ALIASES['overload'], + description='Overload table used when max new connections per time interval has been reached' + ), + adaptive_start=dict(type='int', required=False,), + adaptive_end=dict(type='int', required=False,), + prio=dict( + type='str', required=False, default=RULE_DEFAULTS['prio'], + choices=['', '0', '1', '2', '3', '4', '5', '6', '7'], + description='Match packets which have the given queueing priority assigned' + ), + set_prio=dict( + type='str', required=False, default=RULE_DEFAULTS['set_prio'], + choices=['', '0', '1', '2', '3', '4', '5', '6', '7'], + description='Assigne a specific queueing priority' + ), + set_prio_low=dict( + type='str', required=False, default=RULE_DEFAULTS['set_prio_low'], + choices=['', '0', '1', '2', '3', '4', '5', '6', '7'], + description='Assigne a specific queueing priority to packets which have a TOS of lowdelay ' + 'and TCP ACKs with no data payload' + ), + tag=dict(type='str', required=False, default=RULE_DEFAULTS['tag'],), + tagged=dict(type='str', required=False, default=RULE_DEFAULTS['tagged'],), + tcp_flags=dict( + type='list', elements='str', required=False, default=RULE_DEFAULTS['tcp_flags'], + choices=['syn', 'ack', 'fin', 'rst', 'psh', 'urg', 'ece', 'cwr'], + description='TCP flags that must be set for this rule to match' + ), + tcp_flags_clear=dict( + type='list', elements='str', required=False, default=RULE_DEFAULTS['tcp_flags_clear'], + choices=['syn', 'ack', 'fin', 'rst', 'psh', 'urg', 'ece', 'cwr'], + description='TCP flags that must be cleared for this rule to match' + ), + schedule=dict( + type='str', required=False, default=RULE_DEFAULTS['schedule'], + aliases=RULE_MOD_ARG_ALIASES['schedule'], + ), + tos=dict( + type='str', required=False, default=RULE_DEFAULTS['tos'], + description='Match packets which have the given TOS/DCSP assigned' + ), + description=dict( + type='str', required=False, default=RULE_DEFAULTS['description'], + aliases=RULE_MOD_ARG_ALIASES['description'] + ), + uuid=dict(type='str', required=False, description='Optionally you can supply the uuid of an existing rule'), + icmp_type=dict( + type='list', elements='str', required=False, default=RULE_DEFAULTS['icmp_type'], + aliases=RULE_MOD_ARG_ALIASES['icmp_type'], choices=[ + 'echoreq', 'echorep', 'unreach', 'squench', 'redir', 'althost', 'routeradv', 'routersol', 'timex', + 'paramprob', 'timereq', 'timerep', 'inforeq', 'inforep', 'maskreq', 'maskrep', + ], + description='If protocol is ICMP/IPV6-ICMP you can specify the types' + ), + **STATE_MOD_ARG, + **RULE_MATCH_FIELDS_ARG, +) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/alias.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/alias.py new file mode 100644 index 0000000..7fc19c4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/alias.py @@ -0,0 +1,111 @@ +from re import match as regex_match +from typing import Callable + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + BUILTIN_ALIASES, BUILTIN_INTERFACE_ALIASES_REG +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_valid_partial_mac_address, is_valid_url, validate_port_or_range, is_valid_network, is_valid_host, is_ip +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset + + +# This should be kept aligned with getValidators from the AliasContentField +# https://github.com/opnsense/core/blob/master/src/opnsense/mvc/app/models/OPNsense/Firewall/FieldTypes/AliasContentField.php +def validate_values(cnf: dict, error_func: Callable, existing_entries: dict) -> None: + v_type = cnf['type'] + + if isinstance(existing_entries, dict): + existing_entries = [ + a['name'] + for a in existing_entries.values() + ] + + else: + existing_entries = [ + a['name'] + for a in existing_entries + ] + + for value in cnf['content']: + if value in existing_entries: + continue + + error = f"Value '{value}' is invalid for type '{v_type}'!" + + if v_type == 'port': + validate_port_or_range(module=None, port=value, error_func=error_func, range_sep=':') + + elif v_type == 'host' and not is_valid_host(value): + error_func(error) + + #elif v_type == 'geoip': + # pass + + elif v_type == 'network' and not is_valid_network(value): + error_func(error) + + elif v_type == 'networkgroup': + error_func(f"Value '{value}' is not a valid alias!") + + elif v_type == 'mac' and not is_valid_partial_mac_address(value): + error_func(error) + + elif v_type == 'dynipv6host' and not is_ip(f"0000{value}"): + error_func(error) + + elif v_type == 'asn': + try: + if int(value) < 1 or int(value) > 4294967296: + error_func(error) + + except ValueError: + error_func(error) + + #elif v_type == 'authgroup': + # pass + + # OPNsense has no validation for urls in the API + elif v_type in ['url', 'urltable', 'urljson'] and not is_valid_url(value): + error_func(error) + + +def compare_aliases(existing: dict, configured: dict) -> tuple: + before = list(map(str, existing['content'])) + after = list(map(str, configured['content'])) + before.sort() + after.sort() + return before != after, before, after + + +def builtin_alias(name: str) -> bool: + # ignore built-in aliases + return name in BUILTIN_ALIASES or \ + regex_match(BUILTIN_INTERFACE_ALIASES_REG, name) is not None + + +def filter_builtin_alias(aliases: list) -> list: + filtered = [] + + for alias in aliases: + # ignore built-in aliases + if not builtin_alias(alias['name']): + filtered.append(alias) + + return filtered + + +DEFAULT_UPDATEFREQ_DAYS_URLTABLE = 7 # see: https://github.com/O-X-L/ansible-opnsense/pull/270 + + +def build_updatefreq(updatefreq: (int, float, str), default: bool = False) -> (int, float): + if is_unset(updatefreq): + if default: + return DEFAULT_UPDATEFREQ_DAYS_URLTABLE + + return updatefreq + + updatefreq = float(updatefreq) + dec = 1 + if str(updatefreq).endswith('.0'): + dec = None + + return round(updatefreq, dec) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/api.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/api.py new file mode 100644 index 0000000..dc7ead6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/api.py @@ -0,0 +1,207 @@ +import ssl +from pathlib import Path +from json import JSONDecodeError +from json import dumps as json_dumps +from datetime import datetime + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + DEBUG_CONFIG +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + ensure_list +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_valid_domain, is_ip + + +def _load_credential_file(module: AnsibleModule) -> tuple[(str, None), (str, None)]: + cred_file_info = Path(module.params['api_credential_file']) + + if cred_file_info.is_file(): + cred_file_mode = oct(cred_file_info.stat().st_mode)[-3:] + + if int(cred_file_mode[2]) != 0: + module.warn( + f"Provided 'api_credential_file' at path " + f"'{module.params['api_credential_file']}' is world-readable " + f"(mode {cred_file_mode})!" + ) + + with open(module.params['api_credential_file'], 'r', encoding='utf-8') as file: + config = {} + vaulted = False + + for line in file.readlines(): + try: + key, value = line.split('=', 1) + config[key] = value.strip() + + except ValueError: + if line.startswith('$ANSIBLE_VAULT'): + vaulted = True + break + + if vaulted: + module.fail_json( + f"Credential file '{module.params['api_credential_file']}' " + 'is ansible-vault encrypted! This is not yet supported.' + ) + + if 'key' not in config or 'secret' not in config: + module.fail_json( + f"Credential file '{module.params['api_credential_file']}' " + 'could not be parsed!' + ) + + return config['key'], config['secret'] + + else: + module.fail_json( + f"Provided 'api_credential_file' at path " + f"'{module.params['api_credential_file']}' does not exist!" + ) + + return None, None + + +def check_or_load_credentials(module: AnsibleModule) -> tuple[(str, None), (str, None)]: + if module.params['api_credential_file'] is not None: + return _load_credential_file(module) + + if module.params['api_key'] is None and module.params['api_secret'] is None: + module.fail_json("Neither 'api_key' & 'api_secret' nor 'api_credential_file' were provided!") + + return None, None + + +def check_host(module: AnsibleModule) -> None: + if not is_ip(module.params['firewall']): + fw_dns = module.params['firewall'] + + if fw_dns.find('.') == -1: + # TLD-only will fail the domain validation + fw_dns = f'dummy.{fw_dns}' + + if not is_valid_domain(fw_dns): + module.fail_json(f"Host '{module.params['firewall']}' is neither a valid IP nor Domain-Name!") + + +def ssl_verification(module: AnsibleModule) -> (ssl.SSLContext, bool): + context = ssl.create_default_context() + + if not module.params['ssl_verify']: + context = False + + elif module.params['ssl_ca_file'] is not None: + if Path(module.params['ssl_ca_file']).is_file(): + context.load_verify_locations(cafile=module.params['ssl_ca_file']) + + else: + module.fail_json(f"Provided 'ssl_ca_file' at path '{module.params['ssl_ca_file']}' does not exist!") + + return context + + +def get_params_path(cnf: dict) -> str: + params_path = '' + + if 'params' in cnf and cnf['params'] is not None: + for param in ensure_list(cnf['params']): + params_path += f"/{param}" + + return params_path + + +def _clean_response(response: dict) -> dict: + response_data = response.copy() + for field in [ + 'headers', 'next_request', '_decoder', 'stream', 'extensions', 'history', 'is_closed', + 'is_stream_consumed', 'default_encoding', + ]: + if field in response_data: + response_data.pop(field) + + return response_data + + +def debug_api( + module: AnsibleModule, method: str = None, url: str = None, + data: dict = None, headers: dict = None, response: dict = None, +) -> None: + if 'debug' not in module.params or not module.params['debug']: + return + + if response is not None: + msg = f"RESPONSE: '{_clean_response(response.__dict__)}'" + + else: + msg = f'REQUEST: {method} | URL: {url}' + + if headers is not None: + msg += f" | HEADERS: '{headers}'" + + if data is not None: + msg += f" | DATA: '{json_dumps(data)}'" + + log_path = Path(DEBUG_CONFIG['path_log']) + if not log_path.exists(): + log_path.mkdir() + + with open( + f"{log_path}/{DEBUG_CONFIG['log_api_calls']}", + 'a+', encoding='utf-8' + ) as log: + log.write(f"\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S:%f')} | {method} => {url}\n") + + module.warn(msg) + + +def check_response(module: AnsibleModule, cnf: dict, response) -> dict: + debug_api(module=module, response=response) + + if 'allowed_http_stati' not in cnf: + cnf['allowed_http_stati'] = [200] + + try: + json = response.json() + + except JSONDecodeError: + json = {} + + if response.status_code not in cnf['allowed_http_stati'] or \ + ('result' in json and json['result'] == 'failed'): + # sometimes an error 'hides' behind a 200-code + if f"{response.__dict__}".find('Controller not found') != -1: + module.fail_json( + f"API call failed | Needed plugin not installed! | " + f"Response: {response.__dict__}" + ) + + elif f"{response.__dict__}".find(' in use') != -1: + json['in_use'] = True + + else: + if 'validations' in json: + module.fail_json( + f"API call failed | Error: {json['validations']} | " + f"Response: {response.__dict__}" + ) + + else: + module.fail_json(f"API call failed | Response: {response.__dict__}") + + return json + + +def api_pretty_exception(m: AnsibleModule, method: str, url: str, error): + call = f'{method} => {url}' + msg = f"Unable to connect '{call}'" + + if str(error).find('timed out') != -1: + msg = f"Got timeout calling '{call}'" + + if str(error).find('CERTIFICATE_VERIFY_FAILED') != -1 or str(error).find('certificate verify failed') != -1: + msg = f"SSL verification failed '{url}'! Make sure to follow the the documentation: "\ + "https://ansible-opnsense.oxl.app/usage/2_basic.html#ssl-certificate" + + m.fail_json(f"{msg} ({error})") diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/main.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/main.py new file mode 100644 index 0000000..7b38bd5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/main.py @@ -0,0 +1,409 @@ +from typing import Callable +from functools import reduce + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + exit_bug, exit_cnf + + +def diff_remove_empty(diff: dict, to_none: bool = False) -> dict: + d = diff.copy() + for k in diff: + if len(diff[k]) == 0: + if to_none: + d[k] = None + + else: + d.pop(k) + + return d + + +def ensure_list(data: (int, str, list, None)) -> list: + # if user supplied a string instead of a list => convert it to match our expectations + if isinstance(data, list): + return data + + if data is None: + return [] + + return [data] + + +def get_matching( + module: AnsibleModule, existing_items: (dict, list), compare_item: dict, + match_fields: list, simplify_func: Callable = None, +) -> (dict, None): + matching = None + + if len(existing_items) > 0: + if isinstance(existing_items, dict): + _existing_items_list = [] + for uuid, existing in existing_items.items(): + existing['uuid'] = uuid + _existing_items_list.append(existing) + + existing_items = _existing_items_list + + for existing in existing_items: + _matching = [] + + if simplify_func is not None: + existing = simplify_func(existing) + + try: + if not isinstance(match_fields, list): + exit_bug(f"Failed because 'match_fields' are not a list: {type(match_fields)} '{match_fields}'") + + for field in match_fields: + _matching.append(str(existing[field]) == str(compare_item[field])) + + if module.params['debug']: + if existing[field] != compare_item[field]: + module.warn( + f"NOT MATCHING: " + f"'{existing[field]}' != '{compare_item[field]}'" + ) + + except KeyError as error: + exit_bug( + "Failed to match existing entry with provided one: " + f"{existing} <=> {sanitize_module_args(compare_item)}; " + f"Error while comparing: {error}" + ) + + if all(_matching): + matching = existing + break + + return matching + + +def get_multiple_matching( + module: AnsibleModule, existing_items: (dict, list), compare_item: dict, + match_fields: list, simplify_func: Callable = None, +) -> list: + matching = [] + + if len(existing_items) > 0: + if isinstance(existing_items, dict): + _existing_items_list = [] + for uuid, existing in existing_items.items(): + existing['uuid'] = uuid + _existing_items_list.append(existing) + + existing_items = _existing_items_list + + for existing in existing_items: + _simple = get_matching( + module=module, + existing_items=[existing], + compare_item=compare_item, + match_fields=match_fields, + simplify_func=simplify_func, + ) + if _simple is not None: + matching.append(_simple) + + return matching + + +def is_true(data: (str, int, bool)) -> bool: + return data in [1, '1', True] + + +def get_selected(data: dict) -> (str, None): + if isinstance(data, dict): + for key, values in data.items(): + if is_true(values['selected']): + return key + + return '' # none selected + + # if function is re-applied + return data + + +def get_selected_value(data: (dict, list)) -> (str, None): + if isinstance(data, dict): + for values in data.values(): + if is_true(values['selected']) and 'value' in values: + return values['value'] + + return '' # none selected + + if isinstance(data, list): + for values in data: + if is_true(values['selected']) and 'value' in values: + return values['value'] + + return '' # none selected + + # if function is re-applied + return data + + +def get_selected_opt_list(data: (dict, list)) -> (str, None): + if isinstance(data, dict): + return get_selected(data) + + return get_selected_value(data) + + +def get_selected_opt_list_idx(data: list) -> int: + idx = 0 + for values in data: + if is_true(values['selected']): + return idx + + idx += 1 + + return 0 + + +def get_selected_multi(data: (dict, list), get_value: bool = False) -> list: + if isinstance(data, list) and len(data) > 0 and not isinstance(data[0], dict): + # if function is re-applied + return data + + selected_values = [] + if isinstance(data, dict): + for key, values in data.items(): + if is_true(values['selected']): + if not get_value: + selected_values.append(key) + + if get_value and 'value' in values: + selected_values.append(values['value']) + + if isinstance(data, list): + for values in data: + if is_true(values['selected']) and 'value' in values: + selected_values.append(values['value']) + + return selected_values + + +def get_selected_list(data: dict, remove_empty: bool = False, get_value: bool = False) -> list: + if isinstance(data, list): + if len(data) == 0: + return [] + + if not isinstance(data[0], dict): + # if function is re-applied + return data + + if isinstance(data, str): + if data.strip() == '': + return [] + + return data.split(',') + + selected = get_selected_multi(data=data, get_value=get_value) + if remove_empty: + for key in [None, '', ' ']: + if key in selected: + selected.remove(key) + + if 'System: Deny config write' in selected: + raise exit_bug(f"TEST: {data}") + + selected.sort() + return selected + + +def get_key_by_value_from_selection(selection: dict, value: str) -> (str, None): + if isinstance(selection, dict): + for key, values in selection.items(): + if 'value' in values and values['value'] == value: + return key + + return None + + +def get_key_by_value_end_from_selection(selection: dict, value: str) -> (str, None): + if isinstance(selection, dict): + for key, values in selection.items(): + if 'value' in values and values['value'].endswith(value): + return key + + return None + + +def get_key_by_value_beg_from_selection(selection: dict, value: str) -> (str, None): + if isinstance(selection, dict): + for key, values in selection.items(): + if 'value' in values and values['value'].startswith(value): + return key + + return None + + +def to_digit(data: bool) -> int: + return 1 if data else 0 + + +def get_simple_existing( + entries: (dict, list), add_filter: Callable = None, + simplify_func: Callable = None +) -> list: + simple_entries = [] + + if isinstance(entries, dict): + _entries = [] + for uuid, entry in entries.items(): + if not isinstance(entry, dict): + exit_bug(f"The provided entry is not a dictionary => '{entry}'") + + entry['uuid'] = uuid + _entries.append(entry) + + entries = _entries + + for entry in entries: + if simplify_func is not None and add_filter is not None: + simple_entries.append(add_filter(simplify_func(entry))) + + elif simplify_func is not None: + simple_entries.append(simplify_func(entry)) + + else: + simple_entries.append(entries) + + return simple_entries + + +def format_int(data: (int, str)) -> (int, str, None): + if isinstance(data, int): + return data + + if data is None: + return None + + if data.isnumeric(): + return int(data) + + return data + + +def sort_param_lists(params: dict) -> None: + for k in params: + if isinstance(params[k], list): + try: + params[k].sort() + + except TypeError: + pass + +# pylint: disable=R0914,R0915 +def simplify_translate( + existing: dict, translate: dict = None, typing: dict = None, + bool_invert: list = None, ignore: list = None, value_map: dict = None, +) -> dict: + # pylint: disable=R0912 + simple = {} + if translate is None: + translate = {} + + if typing is None: + typing = {} + + if bool_invert is None: + bool_invert = [] + + if ignore is None: + ignore = [] + + if value_map is None: + value_map = {} + + try: + # translate api-fields to ansible-fields + for k, v in translate.items(): + if v in existing: + simple[k] = existing[v] + elif isinstance(v, tuple): + simple[k] = reduce(lambda e, i: e[i], v, existing) + + translate_fields = translate.values() + for k in existing: + if k not in translate_fields and k not in ignore: + simple[k] = existing[k] + + # correct value types to match (for diff-checks) + for t, fields in typing.items(): + for f in fields: + if f in ignore: + continue + + if t == 'bool': + simple[f] = is_true(simple[f]) + + elif t == 'int': + simple[f] = format_int(simple[f]) + + elif t == 'list': + simple[f] = get_selected_list(data=simple[f], remove_empty=True, get_value=False) + + elif t == 'list_value': + simple[f] = get_selected_list(data=simple[f], remove_empty=True, get_value=True) + + elif t == 'select': + simple[f] = get_selected(simple[f]) + + elif t == 'select_opt_list': + simple[f] = get_selected_opt_list(simple[f]) + + elif t == 'select_opt_list_idx': + simple[f] = get_selected_opt_list_idx(simple[f]) + + for f, vmap in value_map.items(): + try: + for pretty_value, opn_value in vmap.items(): + if simple[f] == opn_value: + simple[f] = pretty_value + break + + except KeyError: + pass + + for k, v in simple.items(): + if isinstance(v, str) and v.isnumeric(): + simple[k] = int(simple[k]) + + elif isinstance(v, bool) and k in bool_invert: + simple[k] = not simple[k] + + except KeyError as err: + exit_bug( + f"Failed to translate API entry to Ansible entry! Maybe the API changed lately? " + f"Failed field: {err} | " + f"API entry: '{existing}' '{simple}'" + ) + + return simple + + +def is_unset(value: (str, None, list, dict)) -> bool: + if isinstance(value, (list, dict)): + return len(value) == 0 + + if isinstance(value, str): + value = value.strip() + + return value in ['', None] + + +def unset_check_error(params: dict, field: str, fail: bool) -> bool: + if is_unset(params[field]): + if fail: + exit_cnf(f"Field '{field}' must be set!") + + return False + + return True + +def sanitize_module_args(args: dict) -> dict: + args.pop('api_key', None) + args.pop('api_secret', None) + return args diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/rule.py new file mode 100644 index 0000000..5e9830b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/rule.py @@ -0,0 +1,44 @@ +from ansible.module_utils.basic import AnsibleModule + + +def validate_values(error_func, module: AnsibleModule, cnf: dict, kind: str = 'filter') -> None: + # error = "Value '%s' is invalid for the field '%s'!" + + # can't validate as aliases are supported + # for field in ['source_net', 'destination_net']: + # if cnf[field] not in [None, '', 'any']: + # try: + # ip_network(cnf[field]) + # + # except ValueError: + # try: + # ip_address(cnf[field]) + # + # except ValueError: + # error_func(error % (cnf[field], field)) + + if kind == 'filter': + required_together=[ + ('max_src_conn_rate', 'max_src_conn_rates', 'overload'), + ('adaptive_start', 'adaptive_end'), + ('tcp_flags', 'tcp_flags_clear'), + ] + for opts in required_together: + if any((cnf[opts[0]] is None) != (cnf[o] is None) for o in opts[1:]): + error_func(f"parameters are required together: {', '.join(opts)}") + if any((cnf[opts[0]] == []) != (cnf[o] == []) for o in opts[1:]): + error_func(f"parameters are required together: {', '.join(opts)}") + + # some recommendations - maybe the user overlooked something + if 'action' in cnf and cnf['action'] == 'pass' and cnf['protocol'] in ['TCP', 'UDP', 'TCP/UDP']: + if cnf['source_net'] == 'any' and cnf['destination_net'] == 'any': + module.warn( + "Configuring allow-rules with 'any' source and " + "'any' destination is bad practice!" + ) + + elif cnf['destination_net'] == 'any' and cnf['destination_port'] == 'any': + module.warn( + "Configuring allow-rules to 'any' destination " + "using 'all' ports is bad practice!" + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/system.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/system.py new file mode 100644 index 0000000..9a25985 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/system.py @@ -0,0 +1,97 @@ +from socket import socket, AF_INET, AF_INET6, SOCK_STREAM, gaierror +from time import time, sleep +from datetime import datetime + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session, HTTPX_EXCEPTIONS +from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import CONNECTION_TEST_TIMEOUT + + +def _opn_reachable_ipv(module: AnsibleModule, address_family: int) -> bool: + with socket(address_family, SOCK_STREAM) as s: + s.settimeout(CONNECTION_TEST_TIMEOUT) + return s.connect_ex(( + module.params['firewall'], + module.params['api_port'] + )) == 0 + + +def _opn_reachable(module: AnsibleModule) -> bool: + try: + return _opn_reachable_ipv(module, AF_INET) + + except gaierror: + return _opn_reachable_ipv(module, AF_INET6) + + +def _wait_msg(module: AnsibleModule, msg: str): + module.warn(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} | {msg}") + + +def wait_for_response(module: AnsibleModule) -> bool: + timeout = time() + module.params['wait_timeout'] + + _wait_msg(module, 'Waiting for services to stop..') + sleep(10) + + while time() < timeout: + poll_interval_start = time() + + if _opn_reachable(module=module): + _wait_msg(module, 'Got response!') + return True + + _wait_msg(module, 'Waiting for response..') + poll_interval_elapsed = time() - poll_interval_start + if poll_interval_elapsed < module.params['poll_interval']: + sleep(module.params['poll_interval'] - poll_interval_elapsed) + + raise TimeoutError + + +def get_upgrade_status(s: Session) -> dict: + return s.get({ + 'command': 'upgradestatus', + 'module': 'core', + 'controller': 'firmware', + }) + + +def wait_for_update(module: AnsibleModule, s: Session) -> bool: + timeout = time() + module.params['wait_timeout'] + + if module.params['action'] == 'upgrade': + _wait_msg(module, 'Waiting for download & upgrade to finish..') + + else: + _wait_msg(module, 'Waiting for update to finish..') + + sleep(2) + + while time() < timeout: + poll_interval_start = time() + + try: + result = get_upgrade_status(s) + status = result['status'] + + _wait_msg(module, f"Got response: {status}") + + if status == 'error' and 'log' in result: + _wait_msg(module, f"Got error: {result['log']}") + return False + + if status == 'done': + _wait_msg(module, f"Got result: {result['log']}") + return True + + except (HTTPX_EXCEPTIONS, ConnectionError, TimeoutError): + # not reachable while rebooting + _wait_msg(module, 'Waiting for response..') + + poll_interval_elapsed = time() - poll_interval_start + if poll_interval_elapsed < module.params['poll_interval']: + sleep(module.params['poll_interval'] - poll_interval_elapsed) + + raise TimeoutError diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/unbound.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/unbound.py new file mode 100644 index 0000000..1818dbe --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/unbound.py @@ -0,0 +1,15 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_valid_domain + + +def validate_domain(module: AnsibleModule, domain: str) -> None: + test_domain = domain + + if domain.find('.') == -1: + # TLD-only will fail the domain validation + test_domain = f'dummy.{domain}' + + if not is_valid_domain(test_domain): + module.fail_json(f"Value '{domain}' is an invalid domain!") diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/utils.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/utils.py new file mode 100644 index 0000000..7b2282a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/utils.py @@ -0,0 +1,58 @@ +from cProfile import Profile +from pstats import Stats +from io import StringIO +from datetime import datetime +from pathlib import Path +from typing import Callable +from inspect import stack as inspect_stack +from inspect import getfile as inspect_getfile + +from httpx import ConnectError, ConnectTimeout + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + DEBUG_CONFIG + + +def profiler( + check: Callable, kwargs: dict, module_name: str = None, + sort: str = 'tottime', show_top_n: int = 20 +) -> (list, dict, bool, None): + # note: https://stackoverflow.com/questions/10326936/sort-cprofile-output-by-percall-when-profiling-a-python-script + # sort options: ncalls, tottime, cumtime + _ = Profile() + _.enable() + + if module_name is None: + module_name = inspect_getfile(inspect_stack()[1][0]).rsplit('/', 1)[1].rsplit('.', 1)[0] + + httpx_error = None + check_response = None + + try: + check_response = check(**kwargs) + + except (ConnectError, ConnectTimeout, ConnectionError) as error: + httpx_error = str(error) + + _.disable() + result = StringIO() + Stats(_, stream=result).sort_stats(sort).print_stats(show_top_n) + cleaned_result = result.getvalue().splitlines()[:-1] + del cleaned_result[1:5] + cleaned_result = '\n'.join(cleaned_result) + + if module_name is not None: + log_path = Path(DEBUG_CONFIG['path_log']) + if not log_path.exists(): + log_path.mkdir() + + with open(f'{log_path}/{module_name}.log', 'a+', encoding='utf-8') as log: + log.write(f"\n{datetime.now().strftime('%Y-%m-%d %H:%M:%S:%f')} | {cleaned_result}\n") + + else: + print(cleaned_result) + + if httpx_error is not None: + print(f"HTTP ERROR: {httpx_error}") + + return check_response diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/validate.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/validate.py new file mode 100644 index 0000000..9386211 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/helper/validate.py @@ -0,0 +1,327 @@ +from typing import Callable +from re import match as regex_match +from re import compile as regex_compile +from re import IGNORECASE as REGEX_IGNORECASE +from re import UNICODE as REGEX_UNICODE +from socket import getservbyname +from ipaddress import ip_address, ip_network, IPv4Address, IPv6Address, IPv6Network, AddressValueError, \ + NetmaskValueError + +from ansible.module_utils.basic import AnsibleModule + +# pylint: disable=W0611 +# (proxied imports) +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset, ensure_list, is_true, unset_check_error +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + exit_bug + +MATCH_DOMAIN = regex_compile( + r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|' + r'([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|' + r'([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.' + r'([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$' +) +MATCH_EMAIL_USER = regex_compile( + # dot-atom + r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+" + r"(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$" + # quoted-string + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|' + r"""\\[\001-\011\013\014\016-\177])*"$)""", + REGEX_IGNORECASE +) +MATCH_EMAIL_DOMAIN = regex_compile( + # domain + r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+' + r'(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?$)' + # literal form, ipv4 address (SMTP 4.1.3) + r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)' + r'(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', + REGEX_IGNORECASE +) +IP_MIDDLE_OCTET = r"(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5]))" +IP_LAST_OCTET = r"(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))" +MATCH_URL_RAW = regex_compile( + r"^" + # protocol identifier + r"(?:(?:https?|ftp)://)" + # user:pass authentication + r"(?:\S+(?::\S*)?@)?" + r"(?:" + r"(?P" + # IP address exclusion + # private & local networks + r"(?:(?:10|127)" + IP_MIDDLE_OCTET + r"{2}" + IP_LAST_OCTET + r")|" + r"(?:(?:169\.254|192\.168)" + IP_MIDDLE_OCTET + IP_LAST_OCTET + r")|" + r"(?:172\.(?:1[6-9]|2\d|3[0-1])" + IP_MIDDLE_OCTET + IP_LAST_OCTET + r"))" + r"|" + # IP address dotted notation octets + # excludes loopback network 0.0.0.0 + # excludes reserved space >= 224.0.0.0 + # excludes network & broadcast addresses + # (first & last IP address of each class) + r"(?P" + r"(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])" + r"" + IP_MIDDLE_OCTET + r"{2}" + r"" + IP_LAST_OCTET + r")" + r"|" + # host name + r"(?:(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)" + # domain name + r"(?:\.(?:[a-z\u00a1-\uffff0-9]-?)*[a-z\u00a1-\uffff0-9]+)*" + # TLD identifier + r"(?:\.(?:[a-z\u00a1-\uffff]{2,}))" + r")" + # port number + r"(?::\d{2,5})?" + # resource path + r"(?:/\S*)?" + # query string + r"(?:\?\S*)?" + r"$", + REGEX_UNICODE | REGEX_IGNORECASE +) +MATCH_URL = regex_compile(MATCH_URL_RAW) +MATCH_MAC_ADDRESS = regex_compile(r'^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$') +MATCH_PARTIAL_MAC_ADDRESS = regex_compile(r'^(?:[0-9a-fA-F]{2}:){1,5}[0-9a-fA-F]{2}$') +# see: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names +MATCH_HOSTNAME = regex_compile(r'^[a-zA-Z0-9-\.]{1,253}$') +MATCH_UUID = regex_compile(r'^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$') + + +def _is_matching(compiled_regex, value: (str, None)) -> bool: + if value is None: + value = '' + + return compiled_regex.match(value) is not None + + +def is_valid_domain(value: str) -> bool: + # see: https://validators.readthedocs.io/en/latest/_modules/validators/domain.html#domain + return _is_matching(compiled_regex=MATCH_DOMAIN, value=value) + + +def is_valid_email(value) -> bool: + # see: https://validators.readthedocs.io/en/latest/_modules/validators/email.html + if not value or '@' not in value: + return False + + email_user, email_domain = value.rsplit('@', 1) + + if not _is_matching(compiled_regex=MATCH_EMAIL_USER, value=email_user): + return False + + if not _is_matching(compiled_regex=MATCH_EMAIL_DOMAIN, value=email_domain): + # Try for possible IDN domain-part + try: + domain_part = email_domain.encode('idna').decode('ascii') + return _is_matching(compiled_regex=MATCH_EMAIL_DOMAIN, value=domain_part) + + except UnicodeError: + return False + + return True + + +def is_valid_url(value: str) -> bool: + # see: https://validators.readthedocs.io/en/latest/_modules/validators/url.html + return _is_matching(compiled_regex=MATCH_URL, value=value) + + +def is_valid_mac_address(value: str) -> bool: + # see: https://validators.readthedocs.io/en/latest/_modules/validators/mac_address.html + return _is_matching(compiled_regex=MATCH_MAC_ADDRESS, value=value) + + +def is_valid_partial_mac_address(value: str) -> bool: + # see: https://validators.readthedocs.io/en/latest/_modules/validators/mac_address.html + return _is_matching(compiled_regex=MATCH_PARTIAL_MAC_ADDRESS, value=value) + + +def is_valid_network(value: str) -> bool: + if '-' in value: + for _value in value.split('-', 1): + if not is_ip(_value): + return False + + return True + + value = value.lstrip('!') + return is_ip_or_network(value) + + +def is_valid_host(value: str) -> bool: + if is_valid_domain(value): + return True + + for _value in value.split('-', 1): + _value = _value.strip('!') + if not is_ip(_value): + return False + + return True + + +def validate_port(module: AnsibleModule, port: int, error_func: Callable = None) -> bool: + if error_func is None: + error_func = module.fail_json + + if is_unset(port): + return True + + if 1 <= int(port) <= 65535: + return True + + error_func(f"Value '{port}' is an invalid port!") + return False + + +def validate_port_or_range(module: AnsibleModule, port: str, error_func: Callable = None, range_sep: str = '-') -> bool: + if error_func is None: + error_func = module.fail_json + + if port == 'any' or is_unset(port): + return True + + for _value in port.split(range_sep, 1): + if _value.isdecimal() and (1 <= int(_value) <= 65535): + continue + + try: + getservbyname(_value) + continue + except OSError: + pass + + error_func(f"Value '{port}' is an invalid port or range!") + return False + + return True + + +def validate_int_fields( + module: AnsibleModule, data: dict, field_minmax: dict, + error_func: Callable = None +): + if error_func is None: + error_func = module.fail_json + + for field, valid in field_minmax.items(): + try: + if ('min' in valid and int(data[field]) < valid['min']) or \ + ('max' in valid and int(data[field]) > valid['max']): + error_func( + f"Value of field '{field}' is not valid - " + f"Must be between {valid['min']} and {valid['max']}!" + ) + + except (TypeError, ValueError): + pass + + +def validate_str_fields( + module: AnsibleModule, data: dict, field_regex: dict = None, + field_minmax_length: dict = None, allow_empty: bool = False, +) -> None: + if field_minmax_length is not None: + for field, min_max_length in field_minmax_length.items(): + if not allow_empty and 'min' in min_max_length and min_max_length['min'] == 0: + allow_empty = True + + if not unset_check_error(params=data, field=field, fail=not allow_empty): + continue + + if 'min' not in min_max_length or 'max' not in min_max_length: + exit_bug("Values of 'STR_LEN_VALIDATIONS' must have a 'min' and 'max' attribute!") + + if min_max_length['min'] < len(str(data[field])) > min_max_length['max']: + module.fail_json( + f"Value of field '{field}' is not valid - " + f"Invalid length must be between {min_max_length['min']} and {min_max_length['max']}!" + ) + + if field_regex is not None: + for field, regex in field_regex.items(): + if not unset_check_error(params=data, field=field, fail=not allow_empty): + continue + + if regex_match(regex, data[field]) is None: + module.fail_json( + f"Value of field '{field}' is not valid - " + f"Must match regex '{regex}'!" + ) + + +def is_ip(host: str, ignore_empty: bool = False, strip_enclosure: bool = True) -> bool: + if ignore_empty and is_unset(host): + return True + + if strip_enclosure and host.startswith('['): + host = host[1:-1] + + try: + ip_address(host) + return True + + except ValueError: + return False + + +def is_ip4(host: str, ignore_empty: bool = False) -> bool: + if ignore_empty and is_unset(host): + return True + + try: + IPv4Address(host) + return True + + except (AddressValueError, NetmaskValueError): + return False + + +def is_ip6(host: str, ignore_empty: bool = False, strip_enclosure: bool = True) -> bool: + if ignore_empty and is_unset(host): + return True + + if strip_enclosure and host.startswith('['): + host = host[1:-1] + + try: + IPv6Address(host) + return True + + except (AddressValueError, NetmaskValueError): + return False + + +def is_network(entry: str, strict: bool = False) -> bool: + try: + ip_network(entry, strict=strict) + return True + + except ValueError: + return False + + +def is_ip_or_network(entry: str, strict: bool = False) -> bool: + valid = is_ip(entry) + + if valid: + return valid + + return is_network(entry=entry, strict=strict) + + +def is_ip6_network(entry: str, strict: bool = False) -> bool: + try: + return isinstance(ip_network(entry, strict=strict), IPv6Network) + + except ValueError: + return False + + +def valid_hostname(name: str) -> bool: + _valid_domain = is_valid_domain(name) + _valid_hostname = _is_matching(compiled_regex=MATCH_HOSTNAME, value=name) + return all([_valid_domain, _valid_hostname]) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_account.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_account.py new file mode 100644 index 0000000..9c397a2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_account.py @@ -0,0 +1,58 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Account(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'update', + 'search': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'acmeclient.accounts.account' + API_MOD = 'acmeclient' + API_CONT = 'accounts' + API_CONT_GET = 'settings' + FIELDS_CHANGE = ['description', 'custom_ca', 'eab_kid', 'eab_hmac'] + FIELDS_ALL = [ + 'enabled', 'name', 'email', 'ca', + ] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['ca'], + } + EXIST_ATTR = 'account' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.account = {} + + def process(self) -> None: + self.b.process() + + if self.p['state'] == 'present' and self.p['register']: + self.register() + + def register(self) -> None: + if self.account.get('statusCode', 100) == 200: + return + + self.r['changed'] = True + if not self.m.check_mode: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.s.post(cnf={ + **self.call_cnf, + 'command': 'register', + }) + + def reload(self): + # no reload required + pass diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_action.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_action.py new file mode 100644 index 0000000..f42b8ff --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_action.py @@ -0,0 +1,85 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Action(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'update', + 'search': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'acmeclient.actions.action' + API_MOD = 'acmeclient' + API_CONT = 'actions' + API_CONT_GET = 'settings' + FIELDS_CHANGE = ['type'] + FIELDS_ALL = [ + 'enabled', 'name', 'description', + # SFTP + 'sftp_host', 'sftp_host_key', 'sftp_port', 'sftp_user', 'sftp_identity_type', + 'sftp_remote_path', 'sftp_chgrp', 'sftp_chmod', 'sftp_chmod_key', + 'sftp_filename_cert', 'sftp_filename_key', 'sftp_filename_ca', + 'sftp_filename_fullchain', + # Remote SSH + 'remote_ssh_host', 'remote_ssh_host_key', 'remote_ssh_port', 'remote_ssh_user', + 'remote_ssh_identity_type', 'remote_ssh_command', + # ACME FRITZ!Box + 'acme_fritzbox_url', 'acme_fritzbox_username', 'acme_fritzbox_password', + # ACME PANOS + 'acme_panos_username', 'acme_panos_password', 'acme_panos_host', + # ACME promox VE + 'acme_proxmoxve_user', 'acme_proxmoxve_server', 'acme_proxmoxve_port', + 'acme_proxmoxve_nodename', 'acme_proxmoxve_realm', 'acme_proxmoxve_tokenid', + 'acme_proxmoxve_tokenkey', + # ACME Vault + 'acme_vault_url', 'acme_vault_prefix', 'acme_vault_token', 'acme_vault_kvv2', + # ACME Synology DSM + 'acme_synology_dsm_hostname', 'acme_synology_dsm_port', 'acme_synology_dsm_scheme', + 'acme_synology_dsm_username', 'acme_synology_dsm_password', 'acme_synology_dsm_create', + 'acme_synology_dsm_deviceid', 'acme_synology_dsm_devicename', + # ACME TrueNAS + 'acme_truenas_apikey', 'acme_truenas_hostname', 'acme_truenas_scheme', + # ACME unifi + 'acme_unifi_keystore', + ] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled', 'acme_vault_kvv2', 'acme_synology_dsm_create'], + 'select': [ + 'type', 'remote_ssh_identity_type', 'acme_synology_dsm_scheme', 'acme_truenas_scheme', + 'sftp_identity_type', + ], + 'int': ['sftp_port', 'remote_ssh_port', 'acme_proxmoxve_port', 'acme_synology_dsm_port'], + } + INT_VALIDATIONS = { + 'sftp_port': {'min': 1, 'max': 65535}, + } + EXIST_ATTR = 'action' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.action = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['type']): + self.m.fail_json('You need to provide type to create/update actions!') + + if self.p['type'].startswith('acme_'): + for field in self.FIELDS_ALL: + if field.startswith(self.p['type']) and is_unset(self.p[field]): + self.m.fail_json(f"You need to provide {field} to create/update {self.p['type']} actions!") + + self._base_check() + + def reload(self): + # no reload required + pass diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_certificate.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_certificate.py new file mode 100644 index 0000000..912fb98 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_certificate.py @@ -0,0 +1,126 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Certificate(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'update', + 'search': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'acmeclient.certificates.certificate' + API_MOD = 'acmeclient' + API_CONT = 'certificates' + API_CONT_GET = 'settings' + FIELDS_CHANGE = [ + 'name', 'alt_names', 'account', 'validation', 'restart_actions', 'auto_renewal', 'renew_interval', 'aliasmode', + 'key_length', 'ocsp' + ] + FIELDS_ALL = [ + 'enabled', 'description', 'domainalias', 'challengealias' + ] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'alt_names': 'altNames', + 'validation': 'validationMethod', + 'key_length': 'keyLength', + 'restart_actions': 'restartActions', + 'auto_renewal': 'autoRenewal', + 'renew_interval': 'renewInterval', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'auto_renewal', 'ocsp'], + 'list': ['alt_names', 'restart_actions'], + 'select': ['account', 'validation', 'restart_actions', 'aliasmode', 'key_length'], + 'int': ['renew_interval'], + } + INT_VALIDATIONS = { + 'renew_interval': {'min': 1, 'max': 5000}, + } + EXIST_ATTR = 'certificate' + SEARCH_ADDITIONAL = { + 'existing_accounts': 'acmeclient.accounts.account', + 'existing_validations': 'acmeclient.validations.validation', + 'existing_actions': 'acmeclient.actions.action', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.certificate = {} + self.existing_accounts = {} + self.existing_validations = {} + self.existing_actions = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['name']): + self.m.fail_json('You need to provide a name to create/update certificates!') + + if self.p['aliasmode'] == 'domain': + self.FIELDS_CHANGE.append('domainalias') + + elif self.p['aliasmode'] == 'challenge': + self.FIELDS_CHANGE.append('challengealias') + + self._base_check() + + if self.p['state'] == 'present': + self._resolve_relations() + + def _resolve_relations(self) -> None: + if is_unset(self.p['account']): + self.m.fail_json('You need to provide an account to create/update certificates!') + + else: + if len(self.existing_accounts) > 0: + for key, values in self.existing_accounts.items(): + if values['name'] == self.p['account']: + self.p['account'] = key + break + + else: + self.m.fail_json(f"Account {self.p['account']} does not exist! {self.existing_accounts}") + + if is_unset(self.p['validation']): + self.m.fail_json('You need to provide the validation to create/update certificates!') + + else: + if len(self.existing_validations) > 0: + for key, values in self.existing_validations.items(): + if values['name'] == self.p['validation']: + self.p['validation'] = key + break + + else: + self.m.fail_json(f"Validation {self.p['validation']} does not exist!") + + if not is_unset(self.p['restart_actions']): + mapping = { + values['name']: key + for key, values in self.existing_actions.items() + } + + missing = [ + action + for action in self.p['restart_actions'] + if action not in mapping + ] + if any(missing): + self.m.fail_json(f"Actions {missing.join(',')} do not exist!") + + self.p['restart_actions'] = [ + mapping[action] + for action in self.p['restart_actions'] + ] + + def reload(self): + # no reload required + pass diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_general.py new file mode 100644 index 0000000..1caa305 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_general.py @@ -0,0 +1,47 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'acmeclient.settings' + API_KEY_PATH_REQ = 'acmeclient.settings' + API_MOD = 'acmeclient' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = [ + 'auto_renewal', 'challenge_port', 'tls_challenge_port', 'restart_timeout', + 'haproxy_integration', 'log_level', 'show_intro', + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'auto_renewal': 'autoRenewal', + 'challenge_port': 'challengePort', + 'tls_challenge_port': 'TLSchallengePort', + 'restart_timeout': 'restartTimeout', + 'haproxy_integration': 'haproxyIntegration', + 'log_level': 'logLevel', + 'show_intro': 'showIntro', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'auto_renewal', 'haproxy_integration', 'show_intro'], + 'select': ['log_level'], + 'int': ['challenge_port', 'tls_challenge_port', 'restart_timeout'], + } + INT_VALIDATIONS = { + 'challenge_port': {'min': 1024, 'max': 65535}, + 'tls_challenge_port': {'min': 1024, 'max': 65535}, + 'restart_timeout': {'min': 0, 'max': 86400}, + } + EXIST_ATTR = 'settings' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_validation.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_validation.py new file mode 100644 index 0000000..630d6b1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/acme_validation.py @@ -0,0 +1,135 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Validation(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'update', + 'search': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'acmeclient.validations.validation' + API_MOD = 'acmeclient' + API_CONT = 'validations' + API_CONT_GET = 'settings' + FIELDS_CHANGE = ['description', 'method'] + FIELDS_ALL = [ + 'name', + 'http_service', 'http_opn_autodiscovery', 'http_opn_interface', 'http_opn_ipaddresses', 'http_haproxy_inject', + 'http_haproxy_frontends', 'tlsalpn_acme_autodiscovery', 'tlsalpn_acme_ipaddresses', 'tlsalpn_acme_interface', + 'dns_service', 'dns_sleep', 'dns_active24_token', 'dns_ad_key', 'dns_ali_key', 'dns_ali_secret', + 'dns_autodns_user', 'dns_autodns_password', 'dns_autodns_context', 'dns_aws_id', 'dns_aws_secret', + 'dns_azuredns_subscriptionid', 'dns_azuredns_tenantid', 'dns_azuredns_appid', 'dns_azuredns_clientsecret', + 'dns_bunny_api_key', 'dns_cf_email', 'dns_cf_key', 'dns_cf_token', 'dns_cf_account_id', 'dns_cf_zone_id', + 'dns_cloudns_auth_id', 'dns_cloudns_sub_auth_id', 'dns_cloudns_auth_password', 'dns_cx_key', 'dns_cx_secret', + 'dns_cyon_user', 'dns_cyon_password', 'dns_ddnss_token', 'dns_dgon_key', 'dns_dnsexit_auth_user', + 'dns_dnsexit_auth_pass', 'dns_dnsexit_api', 'dns_dnshome_password', 'dns_dnshome_subdomain', + 'dns_dnsimple_token', 'dns_dnsservices_user', 'dns_dnsservices_password', 'dns_doapi_token', 'dns_do_pid', + 'dns_do_password', 'dns_domeneshop_token', 'dns_domeneshop_secret', 'dns_dp_id', 'dns_dp_key', + 'dns_duckdns_token', 'dns_dyn_customer', 'dns_dyn_user', 'dns_dyn_password', 'dns_dynu_clientid', + 'dns_dynu_secret', 'dns_freedns_user', 'dns_freedns_password', 'dns_fornex_api_key', 'dns_gandi_livedns_key', + 'dns_gandi_livedns_token', 'dns_gcloud_key', 'dns_googledomains_access_token', 'dns_googledomains_zone', + 'dns_gd_key', 'dns_gd_secret', 'dns_hostingde_server', 'dns_hostingde_apiKey', 'dns_he_user', + 'dns_he_password', 'dns_infoblox_credentials', 'dns_infoblox_server', 'dns_inwx_user', 'dns_inwx_password', + 'dns_inwx_shared_secret', 'dns_ionos_prefix', 'dns_ionos_secret', 'dns_ipv64_token', 'dns_ispconfig_user', + 'dns_ispconfig_password', 'dns_ispconfig_api', 'dns_ispconfig_insecure','dns_jd_id', 'dns_jd_region', + 'dns_jd_secret', 'dns_joker_username', 'dns_joker_password', 'dns_kinghost_username', 'dns_kinghost_password', + 'dns_knot_server', 'dns_knot_key', 'dns_limacity_apikey', 'dns_linode_v4_key', 'dns_loopia_api', + 'dns_loopia_user', 'dns_loopia_password', 'dns_lua_email', 'dns_lua_key', 'dns_miab_user', 'dns_miab_password', + 'dns_miab_server', 'dns_me_key', 'dns_me_secret', 'dns_mydnsjp_masterid', 'dns_mydnsjp_password', + 'dns_mythic_beasts_key', 'dns_mythic_beasts_secret', 'dns_namecheap_user', 'dns_namecheap_api', + 'dns_namecheap_sourceip', 'dns_namecom_user', 'dns_namecom_token', 'dns_namesilo_key', 'dns_nederhost_key', + 'dns_netcup_cid', 'dns_netcup_key', 'dns_netcup_pw', 'dns_njalla_token', 'dns_nsone_key', + 'dns_nsupdate_server', 'dns_nsupdate_zone', 'dns_nsupdate_key', 'dns_oci_cli_user', 'dns_oci_cli_tenancy', + 'dns_oci_cli_region', 'dns_oci_cli_key', 'dns_online_key', 'dns_opnsense_host', 'dns_opnsense_port', + 'dns_opnsense_key', 'dns_opnsense_token', 'dns_opnsense_insecure', 'dns_ovh_app_key', 'dns_ovh_app_secret', + 'dns_ovh_consumer_key', 'dns_ovh_endpoint', 'dns_pleskxml_user', 'dns_pleskxml_pass', 'dns_pleskxml_uri', + 'dns_pdns_url', 'dns_pdns_serverid', 'dns_pdns_token', 'dns_porkbun_key', 'dns_porkbun_secret', 'dns_sl_key', + 'dns_selfhost_user', 'dns_selfhost_password', 'dns_selfhost_map', 'dns_servercow_username', + 'dns_servercow_password', 'dns_simply_api_key', 'dns_simply_account_name', 'dns_transip_username', + 'dns_transip_key', 'dns_udr_user', 'dns_udr_password', 'dns_uno_key', 'dns_uno_user', 'dns_vscale_key', + 'dns_vultr_key', 'dns_yandex_token', 'dns_zilore_key', 'dns_zm_key', 'dns_gdnsdk_user', 'dns_gdnsdk_password', + 'dns_acmedns_user', 'dns_acmedns_password', 'dns_acmedns_subdomain', 'dns_acmedns_updateurl', + 'dns_acmedns_baseurl', 'dns_acmeproxy_endpoint', 'dns_acmeproxy_username', 'dns_acmeproxy_password', + 'dns_variomedia_key', 'dns_schlundtech_user', 'dns_schlundtech_password', 'dns_easydns_apitoken', + 'dns_easydns_apikey', 'dns_euserv_user', 'dns_euserv_password', 'dns_leaseweb_key', 'dns_cn_user', + 'dns_cn_password', 'dns_arvan_token', 'dns_artfiles_username', 'dns_artfiles_password', 'dns_hetzner_token', + 'dns_hexonet_login', 'dns_hexonet_password', 'dns_1984hosting_user', 'dns_1984hosting_password', + 'dns_kas_login', 'dns_kas_authdata', 'dns_kas_authtype', 'dns_desec_token', 'dns_desec_name', + 'dns_infomaniak_token', 'dns_zone_username', 'dns_zone_key', 'dns_dynv6_token', 'dns_cpanel_user', + 'dns_cpanel_token', 'dns_cpanel_hostname', 'dns_regru_username', 'dns_regru_password', 'dns_nic_username', + 'dns_nic_password', 'dns_nic_client', 'dns_nic_secret', 'dns_world4you_username', 'dns_world4you_password', + 'dns_aurora_key', 'dns_aurora_secret', 'dns_conoha_user', 'dns_conoha_password', 'dns_conoha_tenantid', + 'dns_conoha_idapi', 'dns_constellix_key', 'dns_constellix_secret', 'dns_exoscale_key', 'dns_exoscale_secret', + 'dns_internetbs_key', 'dns_internetbs_password', 'dns_pointhq_key', 'dns_pointhq_email', 'dns_rackspace_user', + 'dns_rackspace_key', 'dns_rage4_token', 'dns_rage4_user', + ] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'http_haproxy_inject': 'http_haproxyInject', + 'http_haproxy_frontends': 'http_haproxyFrontends', + } + FIELDS_TYPING = { + 'bool': [ + 'enabled', 'http_opn_autodiscovery', 'http_haproxy_inject', 'tlsalpn_acme_autodiscovery', + 'dns_opnsense_insecure', 'dns_ispconfig_insecure', + ], + 'list': ['http_opn_ipaddresses', 'http_haproxy_frontends', 'tlsalpn_acme_ipaddresses'], + 'select': [ + 'method', 'http_service', 'http_opn_interface', 'tlsalpn_acme_interface', 'dns_service', + 'dns_kas_authtype', + ], + } + EXIST_ATTR = 'validation' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.validation = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['method']): + self.m.fail_json('You need to provide method to create/update validations!') + + if self.p['method'] == 'http01': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['http_service'] + if self.p['http_service'] == 'opnsense': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + [ + field + for field in self.FIELDS_ALL + if field.startswith('http_opn') + ] + + else: + self.FIELDS_CHANGE = self.FIELDS_CHANGE + [ + field + for field in self.FIELDS_ALL + if field.startswith('http_haproxy') + ] + + elif self.p['method'] == 'tlsalpn01': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + [ + field + for field in self.FIELDS_ALL + if field.startswith('tlsalpn_') + ] + + elif self.p['method'] == 'dns01': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['dns_service'] + [ + field + for field in self.FIELDS_ALL + if field.startswith(self.p['dns_service']) + ] + + self._base_check() + + def reload(self): + # no reload required + pass diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/alias.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/alias.py new file mode 100644 index 0000000..d378589 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/alias.py @@ -0,0 +1,137 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + ModuleSoftError +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.alias import \ + validate_values, filter_builtin_alias, build_updatefreq +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_simple_existing, simplify_translate, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Alias(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + 'toggle': 'toggleItem', + } + API_KEY_PATH = 'alias.aliases.alias' + API_MOD = 'firewall' + API_CONT = 'alias' + FIELDS_CHANGE = ['content', 'description'] + FIELDS_ALL = ['name', 'type', 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_ALL.extend(['updatefreq_days', 'interface', 'path_expression']) + FIELDS_TRANSLATE = { + 'updatefreq_days': 'updatefreq', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['type', 'interface'], + } + EXIST_ATTR = 'alias' + JOIN_CHAR = '\n' + TIMEOUT = 20.0 + MAX_ALIAS_LEN = 32 + + def __init__( + self, module: AnsibleModule, result: dict, multi: dict = None, + session: Session = None, fail: dict = None, + ): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail, multi=multi) + self.alias = {} + + def check(self) -> None: + if self.p['type'] == 'urltable': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['updatefreq_days'] + self.p['updatefreq_days'] = build_updatefreq(self.p['updatefreq_days'], default=True) + + if self.p['type'] == 'urljson': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['updatefreq_days', 'path_expression'] + self.p['updatefreq_days'] = build_updatefreq(self.p['updatefreq_days'], default=True) + + if self.p['type'] == 'dynipv6host': + if is_unset(self.p['interface']): + self.m.fail_json('You need to provide an interface to create a dynipv6host alias!') + + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['interface'] + + if len(self.p['name']) > self.MAX_ALIAS_LEN: + self._error( + f"Alias name '{self.p['name']}' is invalid - " + f"must be shorter than {self.MAX_ALIAS_LEN} characters", + ) + + self.b.find(match_fields=[self.FIELD_ID]) + + if self.p['state'] == 'present': + validate_values(error_func=self._error, cnf=self.p, existing_entries=self.existing_entries) + + self._base_check() + + def simplify_existing(self, alias: dict) -> dict: + simple = {} + + if isinstance(alias['content'], dict): + simple['content'] = [item for item in alias['content'].keys() if item != ''] + + else: + # if function is re-applied + return alias + + simple = { + **simplify_translate( + existing=alias, + typing=self.FIELDS_TYPING, + translate=self.FIELDS_TRANSLATE, + ), + **simple, + } + + if simple['type'] in ['urltable', 'urljson']: + simple['updatefreq_days'] = build_updatefreq(alias['updatefreq'], default=False) + + return simple + + def update(self) -> None: + # checking if alias changed + if self.alias['type'] == self.p['type']: + self.b.update() + + else: + self.r['changed'] = True + self._error( + msg=f"Unable to update alias '{self.p[self.FIELD_ID]}' - it is not of the same type! " + f"You need to delete the current one first!", + verification=False, + ) + + def delete(self) -> None: + response = self.b.delete() + + if 'in_use' in response: + self._error( + msg=f"Unable to delete alias '{self.p[self.FIELD_ID]}' as it is currently referenced!", + verification=False, + ) + + def _error(self, msg: str, verification: bool = True) -> None: + if (verification and self.fail_verify) or (not verification and self.fail_process): + self.m.fail_json(msg) + + else: + self.m.warn(msg) + raise ModuleSoftError + + def get_existing(self) -> list: + return filter_builtin_alias( + get_simple_existing( + entries=self.b.search(), + simplify_func=self.simplify_existing, + ) + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_acl.py new file mode 100644 index 0000000..f43e27a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_acl.py @@ -0,0 +1,50 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip_or_network, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Acl(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addAcl', + 'del': 'delAcl', + 'set': 'setAcl', + 'search': 'get', + 'toggle': 'toggleAcl', + } + API_KEY_PATH = 'acl.acls.acl' + API_MOD = 'bind' + API_CONT = 'acl' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['networks'] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['networks'], + } + STR_VALIDATIONS = { + 'name': r'^(?!any$|localhost$|localnets$|none$)[0-9a-zA-Z_\-]{1,32}$' + } + EXIST_ATTR = 'acl' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.acl = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['networks']): + self.m.fail_json('You need to provide at networks to create an ACL!') + + for net in self.p['networks']: + if not is_ip_or_network(net): + self.m.fail_json( + f"It seems you provided an invalid network: '{net}'" + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_blocklist.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_blocklist.py new file mode 100644 index 0000000..9bf55cf --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_blocklist.py @@ -0,0 +1,39 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class Blocklist(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'dnsbl' + API_MOD = 'bind' + API_CONT = 'dnsbl' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'safe_google', 'safe_duckduckgo', 'safe_youtube', 'safe_bing', + 'exclude', 'block', 'enabled', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'block': 'type', + 'exclude': 'whitelists', + 'safe_google': 'forcesafegoogle', + 'safe_duckduckgo': 'forcesafeduckduckgo', + 'safe_youtube': 'forcesafeyoutube', + 'safe_bing': 'forcestrictbing', + } + FIELDS_BOOL_INVERT = ['ipv6', 'prefetch'] + FIELDS_TYPING = { + 'bool': [ + 'safe_google', 'safe_duckduckgo', 'safe_youtube', 'safe_bing', 'enabled', + ], + 'list': ['exclude', 'block'], + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_domain.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_domain.py new file mode 100644 index 0000000..7fd823f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_domain.py @@ -0,0 +1,122 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_selected +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Domain(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addPrimaryDomain', + 'del': 'delDomain', + 'set': 'setDomain', + 'search': 'get', + 'toggle': 'toggleDomain', + } + API_KEY_PATH = 'domain.domains.domain' + API_MOD = 'bind' + API_CONT = 'domain' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'mode', 'primary', 'transfer_key_algo', 'transfer_key_name', 'transfer_key', + 'allow_notify', 'transfer_acl', 'query_acl', 'ttl', 'refresh', 'retry', + 'expire', 'negative', 'admin_mail', 'server', + # 'serial', + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'domainname', + 'mode': 'type', + 'primary': 'primaryip', + 'transfer_key_algo': 'transferkeyalgo', + 'transfer_key_name': 'transferkeyname', + 'transfer_key': 'transferkey', + 'allow_notify': 'allownotifysecondary', + 'transfer_acl': 'allowtransfer', + 'query_acl': 'allowquery', + 'admin_mail': 'mailadmin', + 'server': 'dnsserver', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['primary', 'allow_notify', 'transfer_acl', 'query_acl'], + 'select': ['mode', 'transfer_key_algo'], + } + INT_VALIDATIONS = { + 'ttl': {'min': 60, 'max': 86400}, + 'refresh': {'min': 60, 'max': 86400}, + 'retry': {'min': 60, 'max': 86400}, + 'expire': {'min': 60, 'max': 10000000}, + 'negative': {'min': 60, 'max': 86400}, + } + EXIST_ATTR = 'domain' + # FIELDS_DIFF_EXCLUDE = ['serial'] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.domain = {} + self.existing_acls = None + self.existing_records = None + self.acls_needed = False + + def check(self) -> None: + if self.p['state'] == 'present': + for field in ['allow_notify', 'primary']: + for ip in self.p[field]: + if not is_ip(ip, ignore_empty=True): + self.m.fail_json( + f"It seems you provided an invalid IP address as '{field}': '{is_ip}'" + ) + + if self.p['mode'] != 'primary': + self.CMDS['add'] = 'addSecondaryDomain' + + if not is_unset(self.p['query_acl']) or not is_unset(self.p['transfer_acl']): + self.acls_needed = True + self._search_acls() + + self.b.find(match_fields=[self.FIELD_ID]) + + if self.exists: + if self.p['state'] != 'present': + # checking if domain has any record left before removing it; plugin seems to lack validation + self._search_records() + + if self.existing_records is not None and len(self.existing_records) > 0: + for record in self.existing_records.values(): + if get_selected(record['domain']) == self.domain['uuid']: + self.m.fail_json( + f"Unable to remove domain '{self.domain['name']}' - it has at least " + f"one existing record: '{get_selected(record['type'])}: " + f"{record['name']}.{self.domain['name']}'" + ) + + if self.p['state'] == 'present': + if self.acls_needed: + self.b.find_multiple_links( + field='query_acl', + existing=self.existing_acls, + ) + self.b.find_multiple_links( + field='transfer_acl', + existing=self.existing_acls, + ) + + self._base_check() + + def _search_acls(self) -> None: + self.existing_acls = self.s.get(cnf={ + **self.call_cnf, **{'command': self.CMDS['search'], 'controller': 'acl'} + })['acl']['acls']['acl'] + + def _search_records(self) -> None: + # to check if domain is still in use + self.existing_records = self.s.get(cnf={ + **self.call_cnf, **{'command': self.CMDS['search'], 'controller': 'record'} + })['record']['records']['record'] diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_general.py new file mode 100644 index 0000000..72c7fc4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_general.py @@ -0,0 +1,141 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + simplify_translate +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'general' + API_MOD = 'bind' + API_CONT = 'general' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'ipv6', 'response_policy_zones', 'port', 'listen_ipv4', 'listen_ipv6', 'query_acl', + 'query_source_ipv4', 'query_source_ipv6', 'transfer_source_ipv4', 'transfer_source_ipv6', + 'forwarders', 'filter_aaaa_v4', 'filter_aaaa_v6', 'filter_aaaa_acl', 'log_size', + 'cache_size', 'recursion_acl', 'transfer_acl', 'dnssec_validation', 'hide_hostname', + 'hide_version', 'prefetch', 'ratelimit', 'ratelimit_count', 'ratelimit_except', + 'enabled' + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'ipv6': 'disablev6', + 'response_policy_zones': 'enablerpz', + 'listen_ipv4': 'listenv4', + 'listen_ipv6': 'listenv6', + 'query_source_ipv4': 'querysource', + 'query_source_ipv6': 'querysourcev6', + 'transfer_source_ipv4': 'transfersource', + 'transfer_source_ipv6': 'transfersourcev6', + 'filter_aaaa_v4': 'filteraaaav4', + 'filter_aaaa_v6': 'filteraaaav6', + 'filter_aaaa_acl': 'filteraaaaacl', + 'log_size': 'logsize', + 'cache_size': 'maxcachesize', + 'recursion_acl': 'recursion', + 'transfer_acl': 'allowtransfer', + 'query_acl': 'allowquery', + 'dnssec_validation': 'dnssecvalidation', + 'hide_hostname': 'hidehostname', + 'hide_version': 'hideversion', + 'prefetch': 'disableprefetch', + 'ratelimit': 'enableratelimiting', + 'ratelimit_count': 'ratelimitcount', + 'ratelimit_except': 'ratelimitexcept', + } + FIELDS_BOOL_INVERT = ['ipv6', 'prefetch'] + FIELDS_TYPING = { + 'bool': [ + 'ipv6', 'response_policy_zones', 'filter_aaaa_v4', 'filter_aaaa_v6', 'hide_hostname', + 'hide_version', 'prefetch', 'ratelimit', 'enabled', + ], + 'list': [ + 'ratelimit_except', 'filter_aaaa_acl', 'forwarders', 'listen_ipv6', 'listen_ipv4', + 'transfer_acl', 'query_acl', 'recursion_acl', + ], + 'select': ['dnssec_validation'], + } + INT_VALIDATIONS = { + 'ratelimit_count': {'min': 1, 'max': 1000}, + 'cache_size': {'min': 1, 'max': 99}, + 'log_size': {'min': 1, 'max': 1000}, + 'port': {'min': 1, 'max': 65535}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + self.existing_acls = None + self.acls_needed = False + + def check(self) -> None: + self._check_validators() + + for field in [ + 'listen_ipv4', 'query_source_ipv4', 'transfer_source_ipv4', + 'listen_ipv6', 'query_source_ipv6', 'transfer_source_ipv6', + ]: + if isinstance(self.p[field], list): + for ip in self.p[field]: + if not is_ip(ip, ignore_empty=True): + self.m.fail_json( + f"It seems you provided an invalid IP address as '{field}': '{ip}'" + ) + + if is_unset(self.p[field]): + self.m.fail_json( + f"You need to supply at least one value as '{field}'! " + 'Leave it empty to only use localhost.' + ) + + else: + ip = self.p[field] + if not is_ip(ip, ignore_empty=True): + self.m.fail_json( + f"It seems you provided an invalid IP address as '{field}': '{ip}'" + ) + + if not is_unset(self.p['recursion_acl']) or len(self.p['transfer_acl']) > 0 or len(self.p['query_acl']) > 0: + # to save time on call if not needed + self.acls_needed = True + + self.settings = self.get_existing() + + if self.acls_needed: + self.b.find_multiple_links( + field='recursion_acl', + existing=self.existing_acls, + ) + self.b.find_multiple_links( + field='transfer_acl', + existing=self.existing_acls, + ) + self.b.find_multiple_links( + field='query_acl', + existing=self.existing_acls, + ) + + self._build_diff() + + def get_existing(self) -> dict: + if self.acls_needed: + self.existing_acls = self.s.get(cnf={ + **self.call_cnf, **{'command': self.CMDS['search'], 'controller': 'acl'} + })['acl']['acls']['acl'] + + return simplify_translate( + existing=self.s.get(cnf={ + **self.call_cnf, **{'command': self.CMDS['search']} + })[self.API_KEY_PATH], + translate=self.FIELDS_TRANSLATE, + typing=self.FIELDS_TYPING, + bool_invert=self.FIELDS_BOOL_INVERT, + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_record.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_record.py new file mode 100644 index 0000000..487cae3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/bind_record.py @@ -0,0 +1,222 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + ModuleSoftError +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_multiple_matching +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip4, is_ip6, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_domain import Domain + + +class Record(BaseModule): + MULTI_DIFF_KEY = 'name' + CMDS = { + 'add': 'addRecord', + 'del': 'delRecord', + 'set': 'setRecord', + 'search': 'searchRecord', + 'detail': 'getRecord', + 'toggle': 'toggleRecord', + } + API_KEY_PATH = 'record' + API_MOD = 'bind' + API_CONT = 'record' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['value'] + FIELDS_ALL = ['domain', 'name', 'type', 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['type', 'domain'], + } + FIELDS_RR_MATCH = ['domain', 'name', 'type', 'value'] + EXIST_ATTR = 'record' + + def __init__( + self, module: AnsibleModule, result: dict, multi: dict = None, + session: Session = None, fail: dict = None, + ): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail, multi=multi) + self.existing = [] + self.record = {} + self.existing_entries = None + self.existing_domains = None + self.existing_domain_mapping = None + self.exists = False + self.exists_rr = False + + # pylint: disable=R0915 + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['value']): + self._error( + 'You need to supply a value to create the record ' + f"'{self.p['name']}.{self.p['domain']}'" + ) + + else: + if self.p['type'] == 'A' and not is_ip4(self.p['value']): + self.m.fail_json(f"Value '{self.p['value']}' is not a valid IPv4-address!") + + elif self.p['type'] == 'AAAA' and not is_ip6(self.p['value']): + self.m.fail_json(f"Value '{self.p['value']}' is not a valid IPv6-address!") + + # custom matching as dns round-robin allows for multiple records to match.. + self.search_call_domains() + if self.existing_entries is None: + self.existing_entries = self.get_existing() + + if len(self.existing_domains) == 0: + if self.p['state'] == 'present': + self._error('No existing domain found! Create one before managing its records.') + + else: + domain_found = False + if self.existing_domain_mapping is None: + for uuid, dom in self.existing_domains.items(): + if dom['domainname'] == self.p['domain'] or uuid == self.p['domain']: + self.p['domain'] = uuid + domain_found = True + break + + else: + if self.p['domain'] in self.existing_domain_mapping: + self.p['domain'] = self.existing_domain_mapping[self.p['domain']] + domain_found = True + + if not domain_found: + self._error( + f"The provided domain '{self.p['domain']}' was not found! " + 'You may have to create it before managing its records.' + ) + + self.existing = get_multiple_matching( + module=self.m, existing_items=self.existing_entries, + compare_item=self.p, match_fields=self.p['match_fields'], + simplify_func=self.b.simplify_existing, + ) + + self.exists_rr = len(self.existing) > 1 + self.exists = len(self.existing) == 1 + + if self.exists_rr or self.p.get('round_robin', False): + self.r['diff']['before'] = self.existing + + else: + if self.exists: + self.record = self.existing[0] + self.r['diff']['before'] = self.record + self.call_cnf['params'] = [self.record['uuid']] + + self._base_check() + + def _search_call(self) -> list: + self.search_call_domains() + + existing = [] + for uuid in self.existing_domains: + existing.extend(self.b.api_search_post( + cnf={ + 'module': self.API_MOD, + 'controller': self.API_CONT, + 'command': self.CMDS['search'], + }, + data={'domain': uuid} + )) + + return existing + + def search_call_domains(self): + if self.existing_domains is not None: + return + + data = self.s.get(cnf={ + 'module': Domain.API_MOD, + 'controller': Domain.API_CONT, + 'command': Domain.CMDS['search'], + }) + for k in Domain.API_KEY_PATH.split('.'): + data = data[k] + + self.existing_domains = data + + def _error(self, msg: str, verification: bool = True) -> None: + if (verification and self.fail_verify) or (not verification and self.fail_process): + self.m.fail_json(msg) + + else: + self.m.warn(msg) + raise ModuleSoftError + + def _delete_rr(self) -> None: + self.r['diff']['after'] = {} + + for record in self.existing: + self.call_cnf['params'] = [record['uuid']] + self.delete() + + def process(self) -> None: + if self.exists_rr or self.p['round_robin']: + # round-robin exists + if not self.p['round_robin']: + if self.p['state'] == 'present': + self._error( + msg='Multiple records with the provided domain/type/name combination exist! ' + "To create 'round_robin' records - set the argument to 'true'. " + "Else remove all existing records by re-calling the module with 'state=absent'", + verification=False, + ) + + else: + if self.exists_rr: + self._delete_rr() + + else: + self.delete() + + else: + if self.p['state'] == 'present': + if not self._exists_rr(): + self._diff_rr() + self.create() + + else: + self._delete_rr() + + else: + # single record + self.b.process() + + def _exists_rr(self) -> bool: + # check if exact same record already exists if using round-robin + for e in self.existing: + matching = [] + for f in self.FIELDS_RR_MATCH: + matching.append(e[f] == self.p[f]) + + if all(matching): + return True + + return False + + def _diff_rr(self) -> None: + def _key(item: dict, idx: int) -> str: + return f"{item['type']}:{item['name']}.{item['domain']}#{idx}" + + _before = {} + _after = {} + + _idx = 0 + for e in self.existing: + _before[_key(item=e, idx=_idx)] = e + _idx += 1 + + _new = self.b.build_diff(data=self.p) + _after[_key(item=_new, idx=_idx)] = _new + + self.r['diff']['after'] = {**_before, **_after} + self.r['diff']['before'] = _before diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/cron.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/cron.py new file mode 100644 index 0000000..341f5d3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/cron.py @@ -0,0 +1,67 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class CronJob(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_job', + 'del': 'del_job', + 'set': 'set_job', + 'search': 'get', + 'toggle': 'toggle_job', # test + } + API_KEY_PATH = 'job.jobs.job' + API_MOD = 'cron' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'minutes', 'hours', 'days', 'months', + 'weekdays', 'command', 'who', 'parameters' + ] + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['command'], + 'int': ['minutes', 'hours', 'days', 'months', 'weekdays'], + } + FIELDS_ALL = ['description', 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'cron' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.cron = {} + self.available_commands = [] + + def check(self) -> None: + if self.p['state'] == 'present' and is_unset(self.p['command']): + self.m.fail_json("You need to provide a 'command' if you want to create a cron-job!") + + self.b.find(match_fields=[self.FIELD_ID]) + + if self.p['state'] == 'present': + if self.p['command'] is not None and len(self.available_commands) > 0 and \ + self.p['command'] not in self.available_commands: + self.m.fail_json( + 'Got unsupported command! ' + f"Available ones are: {', '.join(self.available_commands)}" + ) + + self._base_check() + + def _build_all_available_cmds(self, raw_cmds: dict): + if len(self.available_commands) == 0: + for cmd in raw_cmds.keys(): + if cmd not in self.available_commands: + self.available_commands.append(cmd) + + def _simplify_existing(self, existing: dict) -> dict: + simple = self.b.simplify_existing(existing) + simple.pop('origin') + self._build_all_available_cmds(existing['command']) + return simple diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_controlagent.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_controlagent.py new file mode 100644 index 0000000..fab85aa --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_controlagent.py @@ -0,0 +1,40 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class ControlAgent(GeneralModule): + FIELD_ID = 'ip' + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'ctrlagent.general' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'kea' + API_CONT = 'ctrl_agent' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'enabled', 'http_host', 'http_port' + ] + FIELDS_ALL = [*FIELDS_CHANGE] + FIELDS_TYPING = { + 'bool': ['enabled'], + 'int': ['http_port'], + } + INT_VALIDATIONS = { + 'http_port': {'min': 1, 'max': 65535}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + + def check(self) -> None: + if not is_ip(self.p['http_host']): + self.m.fail_json('The provided IP is invalid!') + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_general.py new file mode 100644 index 0000000..5306251 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_general.py @@ -0,0 +1,38 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get' + } + API_KEY_PATH = 'dhcpv4.general' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'kea' + API_CONT = 'dhcpv4' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'enabled', 'interfaces', 'socket_type', 'fw_rules', 'lifetime' + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'lifetime': 'valid_lifetime', + 'fw_rules': 'fwrules', + 'socket_type': 'dhcp_socket_type', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'fw_rules'], + 'int': ['lifetime'], + 'list': ['interfaces'], + 'select': ['socket_type'], + } + INT_VALIDATIONS = { + 'lifetime': {'min': 0}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_reservation_v4.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_reservation_v4.py new file mode 100644 index 0000000..c661752 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_reservation_v4.py @@ -0,0 +1,75 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_network, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class ReservationV4(BaseModule): + FIELD_ID = 'ip' + CMDS = { + 'add': 'add_reservation', + 'del': 'del_reservation', + 'set': 'set_reservation', + 'search': 'search_reservation', + 'detail': 'get_reservation', + } + API_KEY_PATH = 'reservation' + API_MOD = 'kea' + API_CONT = 'dhcpv4' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'mac', 'hostname', 'description', 'subnet' + ] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'select': ['subnet'], + } + FIELDS_TRANSLATE = { + 'ip': 'ip_address', + 'mac': 'hw_address', + } + EXIST_ATTR = 'reservation' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.reservation = {} + self.existing_reservations = None + self.existing_subnets = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['mac']): + self.m.fail_json( + "You need to provide a 'mac' if you want to create a reservation!" + ) + + if is_unset(self.p['subnet']) or not is_network(self.p['subnet']): + self.m.fail_json('The provided subnet is invalid!') + + if not is_ip(self.p['ip']): + self.m.fail_json('The provided IP is invalid!') + + self._base_check() + + if self.p['state'] == 'present': + self._search_subnets() + if not self._find_subnet(): + self.m.fail_json('Provided subnet not found!') + + def _find_subnet(self) -> bool: + for s in self.existing_subnets: + if s['subnet'] == self.p['subnet']: + self.p['subnet'] = s['uuid'] + self.reservation['subnet'] = s['uuid'] + return True + + return False + + def _search_subnets(self): + self.existing_subnets = self.s.get(cnf={ + **self.call_cnf, **{'command': 'searchSubnet'} + })['rows'] diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_subnet_v4.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_subnet_v4.py new file mode 100644 index 0000000..5dc0de7 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcp_subnet_v4.py @@ -0,0 +1,114 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_selected_list, simplify_translate +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class SubnetV4(BaseModule): + CMDS = { + 'add': 'add_subnet', + 'del': 'del_subnet', + 'set': 'set_subnet', + 'search': 'search_subnet', + 'detail': 'get_subnet', + } + API_KEY = 'subnet4' + API_KEY_PATH = 'subnet4' + API_MOD = 'kea' + API_CONT = 'dhcpv4' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'subnet', 'description', 'pools', 'auto_options', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TYPING = { + 'list': ['gateway', 'dns', 'domain_search', 'ntp_servers', 'time_servers'], # 'pools', + 'bool': ['auto_options'], + 'int': ['v6_only_preferred'], + } + FIELDS_TRANSLATE = { + 'auto_options': 'option_data_autocollect', + } + API_ATTR_OPTIONS = 'option_data' + API_FIELDS_OPTIONS = [ + 'gateway', 'routes', 'dns', 'domain', 'domain_search', 'ntp_servers', 'time_servers', + 'next_server', 'tftp_server', 'tftp_file', 'v6_only_preferred', + ] + POOL_JOIN_CHAR = '\n' + FIELDS_TRANSLATE_SPECIAL = { + 'dns': 'domain_name_servers', + 'domain': 'domain_name', + 'gateway': 'routers', + 'routes': 'static_routes', + 'tftp_server': 'tftp_server_name', + 'tftp_file': 'boot_file_name', + } + EXIST_ATTR = 'subnet' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.subnet = {} + self.existing_subnets = None + + def _simplify_existing(self, entry: dict) -> dict: + simple = simplify_translate( + existing=entry, + typing=self.FIELDS_TYPING, + translate=self.FIELDS_TRANSLATE, + ignore=self.API_FIELDS_OPTIONS, + ) + + simple['pools'] = simple['pools'].split(self.POOL_JOIN_CHAR) + if self.API_ATTR_OPTIONS in entry: + # get/details call + opts = entry[self.API_ATTR_OPTIONS] + return { + **simple, + 'dns': get_selected_list(opts[self.FIELDS_TRANSLATE_SPECIAL['dns']]), + 'domain_search': get_selected_list(opts['domain_search']), + 'gateway': get_selected_list(opts[self.FIELDS_TRANSLATE_SPECIAL['gateway']]), + 'routes': opts[self.FIELDS_TRANSLATE_SPECIAL['routes']], + 'domain': opts[self.FIELDS_TRANSLATE_SPECIAL['domain']], + 'ntp_servers': get_selected_list(opts['ntp_servers']), + 'time_servers': get_selected_list(opts['time_servers']), + 'tftp_server': opts[self.FIELDS_TRANSLATE_SPECIAL['tftp_server']], + 'tftp_file': opts[self.FIELDS_TRANSLATE_SPECIAL['tftp_file']], + 'v6_only_preferred': opts['v6_only_preferred'], + } + + # search-call :'( + return { + **simple, + 'dns': entry[f"option_data.{self.FIELDS_TRANSLATE_SPECIAL['dns']}"], + 'domain_search': entry['option_data.domain_search'], + 'gateway': entry[f"option_data.{self.FIELDS_TRANSLATE_SPECIAL['gateway']}"], + 'routes': entry[f"option_data.{self.FIELDS_TRANSLATE_SPECIAL['routes']}"], + 'domain': entry[f"option_data.{self.FIELDS_TRANSLATE_SPECIAL['domain']}"], + 'ntp_servers': entry['option_data.ntp_servers'], + 'time_servers': entry['option_data.time_servers'], + 'tftp_server': entry[f"option_data.{self.FIELDS_TRANSLATE_SPECIAL['tftp_server']}"], + 'tftp_file': entry[f"option_data.{self.FIELDS_TRANSLATE_SPECIAL['tftp_file']}"], + 'v6_only_preferred': entry['option_data.v6_only_preferred'], + } + + def _build_request(self) -> dict: + raw_request = self.b.build_request(ignore_fields=self.API_FIELDS_OPTIONS) + + raw_request[self.API_KEY]['pools'] = self.POOL_JOIN_CHAR.join(self.p['pools']) + raw_request[self.API_KEY][self.API_ATTR_OPTIONS] = { + self.FIELDS_TRANSLATE_SPECIAL['dns']: self.b.RESP_JOIN_CHAR.join(self.p['dns']), + self.FIELDS_TRANSLATE_SPECIAL['gateway']: self.b.RESP_JOIN_CHAR.join(self.p['gateway']), + self.FIELDS_TRANSLATE_SPECIAL['routes']: self.p['routes'], + self.FIELDS_TRANSLATE_SPECIAL['domain']: self.p['domain'], + self.FIELDS_TRANSLATE_SPECIAL['tftp_server']: self.p['tftp_server'], + self.FIELDS_TRANSLATE_SPECIAL['tftp_file']: self.p['tftp_file'], + 'ntp_servers': self.b.RESP_JOIN_CHAR.join(self.p['ntp_servers']), + 'time_servers': self.b.RESP_JOIN_CHAR.join(self.p['time_servers']), + 'domain_search': self.b.RESP_JOIN_CHAR.join(self.p['domain_search']), + 'v6_only_preferred': self.p['v6_only_preferred'], + } + + return raw_request diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcrelay_destination.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcrelay_destination.py new file mode 100644 index 0000000..5eaa768 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcrelay_destination.py @@ -0,0 +1,41 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class DhcRelayDestination(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_dest', + 'del': 'del_dest', + 'set': 'set_dest', + 'search': 'get', + } + API_KEY_PATH = 'dhcrelay.destinations' + API_KEY_PATH_REQ = 'destination' + API_MOD = 'dhcrelay' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['server'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'list': ['server'], + } + EXIST_ATTR = 'destination' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.destination = {} + + def check(self) -> None: + + if self.p['state'] == 'present': + if is_unset(self.p['server']): + self.m.fail_json("You need to provide list of 'server' to create a dhcrelay_destination!") + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcrelay_relay.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcrelay_relay.py new file mode 100644 index 0000000..7e29a8d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dhcrelay_relay.py @@ -0,0 +1,58 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class DhcRelayRelay(BaseModule): + FIELD_ID = 'interface' + CMDS = { + 'add': 'add_relay', + 'del': 'del_relay', + 'set': 'set_relay', + 'search': 'get', + 'toggle': 'toggle_relay', + } + API_KEY_PATH = 'dhcrelay.relays' + API_KEY_PATH_REQ = 'relay' + API_MOD = 'dhcrelay' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['destination', 'agent_info'] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'select': ['interface', 'destination'], + 'bool': ['enabled', 'agent_info'] + } + EXIST_ATTR = 'relay' + SEARCH_ADDITIONAL = { + 'existing_destinations': 'dhcrelay.destinations', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.relay = {} + self.existing_destinations = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['destination']): + self.m.fail_json("You need to provide a 'destination' to create a dhcrelay_relay!") + + self._base_check() + + if not is_unset(self.p['destination']) and self.existing_destinations: + for key, values in self.existing_destinations.items(): + if values['name'] == self.p['destination']: + self.p['destination'] = key + break + + def get_existing(self) -> list: + existing = self.b.get_existing() + for relay in existing: + relay['destination'] = self.existing_destinations[relay['destination']]['name'] + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_boot.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_boot.py new file mode 100644 index 0000000..d88af37 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_boot.py @@ -0,0 +1,54 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Boot(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_boot', + 'del': 'del_boot', + 'set': 'set_boot', + 'search': 'get', + } + API_KEY_PATH = 'dnsmasq.dhcp_boot' + API_KEY_PATH_REQ = 'boot' + API_MOD = 'dnsmasq' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['address', 'filename', 'interface', 'servername', 'tag'] + FIELDS_TYPING = { + 'select': ['interface'], + 'list': ['tag'], + } + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'boot' + SEARCH_ADDITIONAL = { + 'existing_tag': 'dnsmasq.dhcp_tags', + 'existing_interface': 'dnsmasq.interface', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.boot = {} + self.existing_tag = {} + self.existing_interface = {} + + def check(self) -> None: + self._base_check() + + if self.p['state'] == 'present': + self.b.find_single_link( + field='interface', + existing=self.existing_interface, + existing_field_id='value', + ) + self.b.find_multiple_links( + field='tag', + existing_field_id='tag', + existing=self.existing_tag, + fail_soft=True, fail=False, + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_domain.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_domain.py new file mode 100644 index 0000000..a763d96 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_domain.py @@ -0,0 +1,39 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Domain(BaseModule): + FIELD_ID = 'domain' + CMDS = { + 'add': 'add_domain', + 'del': 'del_domain', + 'set': 'set_domain', + 'search': 'get', + } + API_KEY_PATH = 'dnsmasq.domainoverrides' + API_KEY_PATH_REQ = 'domainoverride' + API_MOD = 'dnsmasq' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['sequence', 'ipset', 'src_ip', 'port', 'ip', 'description'] + FIELDS_TRANSLATE = { + 'description': 'descr', + 'src_ip': 'srcip', + } + FIELDS_TYPING = { + 'select': ['ipset'], + 'int': ['sequence', 'port'], + } + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'domain' + INT_VALIDATIONS = { + 'sequence': {'min': 1, 'max': 99999}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.domain = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_general.py new file mode 100644 index 0000000..eeff768 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_general.py @@ -0,0 +1,87 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get' + } + API_KEY_PATH = 'dnsmasq' + API_MOD = 'dnsmasq' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'enabled', 'interfaces', 'regdhcp', 'regdhcpstatic', 'domain_needed', 'port', 'dnssec', + 'resolve_etc_hosts', 'dhcpfirst', 'strict_order', 'strictbind', 'forward_private_reverse', 'log_queries', + 'ident', 'regdhcpdomain', 'dns_forward_max', 'cache_size', 'local_ttl', 'resolv_system', + 'add_mac', 'add_subnet', 'add_subnet', 'dhcp_disable_interfaces', 'dhcp_fqdn', 'dhcp_domain', + 'dhcp_local', 'dhcp_lease_max', 'dhcp_authoritative', 'dhcp_reply_delay', 'dhcp_default_fw_rules', + 'dhcp_enable_ra', 'dhcp_hasync', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'enabled': 'enable', + 'interfaces': 'interface', + 'resolve_etc_hosts': 'no_hosts', + 'ident': 'no_ident', + 'resolv_system': 'no_resolv', + 'forward_private_reverse': 'no_private_reverse', + 'dhcp_disable_interfaces': ('dhcp', 'no_interface'), + 'dhcp_fqdn': ('dhcp', 'fqdn'), + 'dhcp_domain': ('dhcp', 'domain'), + 'dhcp_local': ('dhcp', 'local'), + 'dhcp_lease_max': ('dhcp', 'lease_max'), + 'dhcp_authoritative': ('dhcp', 'authoritative'), + 'dhcp_default_fw_rules': ('dhcp', 'default_fw_rules'), + 'dhcp_reply_delay': ('dhcp', 'reply_delay'), + 'dhcp_enable_ra': ('dhcp', 'enable_ra'), + 'dhcp_hasync': ('dhcp', 'nosync'), + } + FIELDS_BOOL_INVERT = ['resolve_etc_hosts', 'ident', 'forward_private_reverse', 'dhcp_hasync'] + FIELDS_TYPING = { + 'bool': [ + 'enabled','regdhcp','regdhcpstatic','domain_needed', 'dnssec', 'resolve_etc_hosts', 'dhcpfirst', + 'strict_order', 'strictbind', 'forward_private_reverse', 'log_queries', 'ident', 'resolv_system', + 'add_subnet', 'add_subnet', 'dhcp_fqdn', 'dhcp_local', 'dhcp_authoritative', 'dhcp_default_fw_rules', + 'dhcp_enable_ra', 'dhcp_hasync', + ], + 'int': ['port', 'dns_forward_max', 'cache_size', 'local_ttl', 'dhcp_lease_max', 'dhcp_reply_delay'], + 'list': ['interfaces', 'dhcp_disable_interfaces'], + 'str': ['regdhcpdomain'], + 'select': ['add_mac'], + } + SEARCH_ADDITIONAL = { + 'existing_interfaces': 'dnsmasq.interface', + } + INT_VALIDATIONS = { + 'port': {'min': 0, 'max': 65535}, + 'dns_forward_max': {'min': 0}, + 'cache_size': {'min': 0}, + 'local_ttl': {'min': 0}, + 'dhcp_lease_max': {'min': 0}, + 'dhcp_reply_delay': {'min': 0, 'max': 60}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + self.existing_interfaces = {} + + def check(self) -> None: + self._base_check() + + self.b.find_multiple_links( + field='interfaces', + existing_field_id='value', + existing=self.existing_interfaces, + fail_soft=False, fail=False, + ) + self.b.find_multiple_links( + field='dhcp_disable_interfaces', + existing_field_id='value', + existing=self.existing_interfaces, + fail_soft=False, fail=False, + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_host.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_host.py new file mode 100644 index 0000000..49bb792 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_host.py @@ -0,0 +1,64 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_valid_domain +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Host(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_host', + 'del': 'del_host', + 'set': 'set_host', + 'search': 'get', + } + API_KEY_PATH = 'dnsmasq.hosts' + API_KEY_PATH_REQ = 'host' + API_MOD = 'dnsmasq' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'host', 'domain', 'local', 'ip', 'aliases', 'cnames', 'client_id', 'hardware_addr', 'lease_time', 'ignore', + 'set_tag', 'comments', + ] + FIELDS_TRANSLATE = { + 'description': 'descr', + 'hardware_addr': 'hwaddr', + } + FIELDS_TYPING = { + 'list': ['ip', 'aliases', 'cnames', 'hardware_addr'], + 'select': ['set_tag'], + 'bool': ['local', 'ignore'], + 'int': ['lease_time'], + } + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'host' + SEARCH_ADDITIONAL = { + 'existing_tag': 'dnsmasq.dhcp_tags', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.host = {} + self.existing_tag = {} + + def check(self) -> None: + if not is_unset(self.p['domain']) and not is_valid_domain(self.p['domain']): + self.m.fail_json( + f"Value of domain '{self.p['domain']}' is not a valid domain-name!" + ) + + self._base_check() + + if self.p['state'] == 'present': + self.b.find_single_link( + field='set_tag', + existing=self.existing_tag, + existing_field_id='tag', + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_option.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_option.py new file mode 100644 index 0000000..eb63edd --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_option.py @@ -0,0 +1,70 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Option(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_option', + 'del': 'del_option', + 'set': 'set_option', + 'search': 'get', + } + API_KEY_PATH = 'dnsmasq.dhcp_options' + API_KEY_PATH_REQ = 'option' + API_MOD = 'dnsmasq' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['type', 'option', 'option6', 'interface', 'tag', 'set_tag', 'value', 'force'] + FIELDS_TYPING = { + 'select': ['type', 'option', 'option6', 'interface', 'set_tag'], + 'list': ['tag'], + 'bool': ['force'], + } + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'option' + SEARCH_ADDITIONAL = { + 'existing_tag': 'dnsmasq.dhcp_tags', + 'existing_interface': 'dnsmasq.interface', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.option = {} + self.existing_tag = {} + self.existing_interface = {} + + def check(self) -> None: + if self.p['state'] == 'present' and self.p['type'] == 'match': + if is_unset(self.p['set_tag']): + self.m.fail_json("When type is 'match', a set_tag must be set.") + if not is_unset(self.p['interface']): + self.m.fail_json("When type is 'match', no internet can be set.") + if not is_unset(self.p['tag']): + self.m.fail_json("When type is 'match', no tag can be set.") + + self._base_check() + + if self.p['state'] == 'present': + self.b.find_single_link( + field='interface', + existing=self.existing_interface, + existing_field_id='value', + ) + self.b.find_single_link( + field='set_tag', + existing=self.existing_tag, + existing_field_id='tag', + ) + self.b.find_multiple_links( + field='tag', + existing_field_id='tag', + existing=self.existing_tag, + fail_soft=True, fail=False, + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_range.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_range.py new file mode 100644 index 0000000..b6792fa --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_range.py @@ -0,0 +1,79 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip6 +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Range(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_range', + 'del': 'del_range', + 'set': 'set_range', + 'search': 'get', + } + API_KEY_PATH = 'dnsmasq.dhcp_ranges' + API_KEY_PATH_REQ = 'range' + API_MOD = 'dnsmasq' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'interface', 'set_tag', 'start_addr', 'end_addr', 'subnet_mask', 'constructor', 'mode', + 'prefix_len', 'lease_time', 'domain_type', 'domain', 'sync', 'ra_mode', 'ra_priority', + 'ra_mtu', 'ra_interval', 'ra_router_lifetime' + ] + FIELDS_BOOL_INVERT = ['sync'] + FIELDS_TRANSLATE = { + 'sync': 'nosync', + } + FIELDS_TYPING = { + 'select': ['interface', 'set_tag', 'constructor', 'mode', 'domain_type', 'ra_priority'], + 'list': ['ra_mode'], + 'int': ['prefix_len', 'lease_time', 'ra_mtu', 'ra_interval', 'ra_router_lifetime'], + 'bool': ['sync'], + } + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'option' + SEARCH_ADDITIONAL = { + 'existing_tag': 'dnsmasq.dhcp_tags', + 'existing_interface': 'dnsmasq.interface', + } + INT_VALIDATIONS = { + 'prefix_len': {'min': 1, 'max': 64}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.option = {} + self.existing_tag = {} + self.existing_interface = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_ip6(self.p['start_addr']): + pass + else: + self.p['prefix_len'] = '' + + self._base_check() + + if self.p['state'] == 'present': + self.b.find_single_link( + field='interface', + existing=self.existing_interface, + existing_field_id='value', + ) + self.b.find_single_link( + field='constructor', + existing=self.existing_interface, + existing_field_id='value', + ) + self.b.find_single_link( + field='set_tag', + existing=self.existing_tag, + existing_field_id='tag', + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_tag.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_tag.py new file mode 100644 index 0000000..3be9546 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/dnsmasq_tag.py @@ -0,0 +1,30 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Tag(BaseModule): + FIELD_ID = 'tag' + CMDS = { + 'add': 'add_tag', + 'del': 'del_tag', + 'search': 'get', + } + API_KEY_PATH = 'dnsmasq.dhcp_tags' + API_KEY_PATH_REQ = 'tag' + API_MOD = 'dnsmasq' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [] + FIELDS_TYPING = {} + FIELDS_ALL = ['tag'] + EXIST_ATTR = 'tag' + STR_VALIDATIONS = { + 'tag': r'^[0-9a-zA-Z]{1,1024}$' + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.tag = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bfd_neighbor.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bfd_neighbor.py new file mode 100644 index 0000000..9490356 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bfd_neighbor.py @@ -0,0 +1,41 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip_or_network +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Neighbor(BaseModule): + FIELD_ID = 'ip' + CMDS = { + 'add': 'addNeighbor', + 'del': 'delNeighbor', + 'set': 'setNeighbor', + 'search': 'get', + 'toggle': 'toggleNeighbor', + } + API_KEY_PATH = 'bfd.neighbors.neighbor' + API_MOD = 'quagga' + API_CONT = 'bfd' + FIELDS_CHANGE = ['description'] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'neighbor' + FIELDS_TRANSLATE = { + 'ip': 'address', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.neighbor = {} + + def check(self) -> None: + if not is_ip_or_network(self.p[self.FIELD_ID]): + self.m.fail_json(f"Value '{self.p[self.FIELD_ID]}' is not a valid IP address!") + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_as_path.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_as_path.py new file mode 100644 index 0000000..8a16ae6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_as_path.py @@ -0,0 +1,50 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class AsPath(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'addAspath', + 'del': 'delAspath', + 'set': 'setAspath', + 'search': 'get', + 'toggle': 'toggleAspath', + } + API_KEY_PATH = 'bgp.aspaths.aspath' + API_MOD = 'quagga' + API_CONT = 'bgp' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['number', 'action', 'as_pattern'] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'as_pattern': 'as', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + } + INT_VALIDATIONS = { + 'number': {'min': 10, 'max': 99}, + } + EXIST_ATTR = 'as_path' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.as_path = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['number']) or is_unset(self.p['as_pattern']) or is_unset(self.p['action']): + self.m.fail_json( + 'To create a BGP as-path you need to provide a number, ' + 'as_pattern and action!' + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_community_list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_community_list.py new file mode 100644 index 0000000..c25c0fb --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_community_list.py @@ -0,0 +1,51 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Community(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'addCommunitylist', + 'del': 'delCommunitylist', + 'set': 'setCommunitylist', + 'search': 'get', + 'toggle': 'toggleCommunitylist', + } + API_KEY_PATH = 'bgp.communitylists.communitylist' + API_MOD = 'quagga' + API_CONT = 'bgp' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['number', 'seq', 'action', 'community'] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'seq': 'seqnumber', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + } + INT_VALIDATIONS = { + 'number': {'min': 1, 'max': 500}, + 'seq': {'min': 10, 'max': 99}, + } + EXIST_ATTR = 'community_list' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.community_list = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['number']) or is_unset(self.p['seq']) or is_unset(self.p['action']): + self.m.fail_json( + 'To create a BGP community-list you need to provide a number, ' + 'sequence-number and action!' + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_general.py new file mode 100644 index 0000000..0b4a89c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_general.py @@ -0,0 +1,39 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'bgp' + API_MOD = 'quagga' + API_CONT = 'bgp' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'as_number', 'id', 'graceful', 'enabled', 'networks', 'distance', 'log_neighbor_changes', + 'network_import_check', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'as_number': 'asnumber', + 'id': 'routerid', + 'log_neighbor_changes': 'logneighborchanges', + 'network_import_check': 'networkimportcheck', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'graceful', 'network_import_check', 'log_neighbor_changes'], + 'list': ['networks'], + 'int': ['distance', 'as_number'], + } + INT_VALIDATIONS = { + 'as_number': {'min': 1, 'max': 4294967295}, + 'distance': {'min': 1, 'max': 255}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_neighbor.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_neighbor.py new file mode 100644 index 0000000..2611433 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_neighbor.py @@ -0,0 +1,173 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Neighbor(BaseModule): + CMDS = { + 'add': 'addNeighbor', + 'del': 'delNeighbor', + 'set': 'setNeighbor', + 'search': 'get', + 'toggle': 'toggleNeighbor', + } + API_KEY_PATH = 'bgp.neighbors.neighbor' + API_MOD = 'quagga' + API_CONT = 'bgp' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'ip', 'as_number', 'password', 'weight', 'local_ip', 'source_int', + 'ipv6_link_local_int', 'next_hop_self', 'next_hop_self_all', + 'multi_hop', 'multi_protocol', 'rrclient', 'bfd', 'send_default_route', + 'as_override', 'disable_connected_check', 'keepalive', 'hold_down', + 'connect_timer', 'description', 'prefix_list_in', 'prefix_list_out', + 'route_map_in', 'route_map_out', 'remote_as_mode', + ] + FIELDS_DIFF_NO_LOG = ['password'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'as_number': 'remoteas', + 'ip': 'address', + 'local_ip': 'localip', + 'source_int': 'updatesource', + 'ipv6_link_local_int': 'linklocalinterface', + 'next_hop_self': 'nexthopself', + 'next_hop_self_all': 'nexthopselfall', + 'multi_hop': 'multihop', + 'multi_protocol': 'multiprotocol', + 'hold_down': 'holddown', + 'connect_timer': 'connecttimer', + 'send_default_route': 'defaultoriginate', + 'as_override': 'asoverride', + 'prefix_list_in': 'linkedPrefixlistIn', + 'prefix_list_out': 'linkedPrefixlistOut', + 'route_map_in': 'linkedRoutemapIn', + 'route_map_out': 'linkedRoutemapOut', + } + FIELDS_TYPING = { + 'bool': [ + 'next_hop_self', 'next_hop_self_all', 'multi_hop', 'multi_protocol', 'enabled', + 'rrclient', 'bfd', 'send_default_route', 'as_override', 'disable_connected_check', + ], + 'select': [ + 'source_int', 'ipv6_link_local_int', 'prefix_list_in', 'prefix_list_out', + 'route_map_in', 'route_map_out', 'remote_as_mode', + ], + } + INT_VALIDATIONS = { + 'as_number': {'min': 1, 'max': 4294967295}, + 'weight': {'min': 0, 'max': 65535}, + 'keepalive': {'min': 1, 'max': 1000}, + 'hold_down': {'min': 3, 'max': 3000}, + 'connect_timer': {'min': 1, 'max': 65000}, + } + EXIST_ATTR = 'neighbor' + SEARCH_ADDITIONAL = { + 'existing_prefixes': 'bgp.prefixlists.prefixlist', + 'existing_maps': 'bgp.routemaps.routemap', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.neighbor = {} + self.existing_prefixes = None + self.existing_maps = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['ip']): + self.m.fail_json('To create a BGP neighbor you need to provide its peer-ip!') + + if not is_ip(self.p['ip']): + self.m.fail_json(f"Provided peer IP '{self.p['ip']}' is not a valid IP-Address!") + + if not is_unset(self.p['local_ip']) and not is_ip(self.p['local_ip']): + self.m.fail_json(f"Provided source IP '{self.p['local_ip']}' is not a valid IP-Address!") + + self._base_check() + self._find_links() + + def _find_links(self) -> None: + links = { + 'prefix-list-in': { + 'found': False, + 'existing': self.existing_prefixes, + 'match_fields': {'name': 'prefix_list_in'} + }, + 'prefix-list-out': { + 'found': False, + 'existing': self.existing_prefixes, + 'match_fields': {'name': 'prefix_list_out'} + }, + 'route-map-in': { + 'found': False, + 'existing': self.existing_maps, + 'match_fields': {'name': 'route_map_in'} + }, + 'route-map-out': { + 'found': False, + 'existing': self.existing_maps, + 'match_fields': {'name': 'route_map_out'} + }, + } + + for key, values in links.items(): + value_name = values['match_fields']['name'] + provided = not is_unset(self.p[value_name]) + seq_uuid_mapping = {} + + if not provided: + continue + + if len(values['existing']) > 0: + for uuid, entry in values['existing'].items(): + matching = [] + + for api_field, ans_field in values['match_fields'].items(): + if not is_unset(self.p[ans_field]): + matching.append(str(entry[api_field]) == str(self.p[ans_field])) + + if all(matching): + self.p[value_name] = uuid + values['found'] = True + + if 'seqnumber' in entry: + seq_uuid_mapping[int(entry['seqnumber'])] = uuid + + if not values['found']: + self.m.fail_json( + f"Provided {key} '{value_name}' was not found!" + ) + + if len(seq_uuid_mapping) > 0: + # only the lowest prefix-list uuid is linkable - all others are just extensions of the first one + self.p[value_name] = seq_uuid_mapping[min(seq_uuid_mapping.keys())] + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if entry['prefix_list_in'] not in [None, ''] and \ + entry['prefix_list_in'] in self.existing_prefixes: + entry['prefix_list_in'] = self.existing_prefixes[entry['prefix_list_in']]['name'] + + if entry['prefix_list_out'] not in [None, ''] and \ + entry['prefix_list_out'] in self.existing_prefixes: + entry['prefix_list_out'] = self.existing_prefixes[entry['prefix_list_out']]['name'] + + if entry['route_map_in'] not in [None, ''] and \ + entry['route_map_in'] in self.existing_maps: + entry['route_map_in'] = self.existing_maps[entry['route_map_in']]['name'] + + if entry['route_map_out'] not in [None, ''] and \ + entry['route_map_out'] in self.existing_maps: + entry['route_map_out'] = self.existing_maps[entry['route_map_out']]['name'] + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_peer_group.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_peer_group.py new file mode 100644 index 0000000..0d5a91f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_peer_group.py @@ -0,0 +1,148 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class PeerGroup(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addPeergroup', + 'del': 'delPeergroup', + 'set': 'setPeergroup', + 'search': 'get', + 'toggle': 'togglePeergroup', + } + API_KEY_PATH = 'bgp.peergroups.peergroup' + API_MOD = 'quagga' + API_CONT = 'bgp' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'as_mode', 'as_number', 'source_int', 'next_hop_self', 'send_default_route', + 'prefix_list_in', 'prefix_list_out', 'route_map_in', 'route_map_out', 'listen_ranges', + ] + FIELDS_ALL = ['enabled', 'name'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'as_mode': 'remote_as_mode', + 'as_number': 'remoteas', + 'source_int': 'updatesource', + 'next_hop_self': 'nexthopself', + 'send_default_route': 'defaultoriginate', + 'prefix_list_in': 'linkedPrefixlistIn', + 'prefix_list_out': 'linkedPrefixlistOut', + 'route_map_in': 'linkedRoutemapIn', + 'route_map_out': 'linkedRoutemapOut', + 'listen_ranges': 'listenranges', + } + FIELDS_TYPING = { + 'bool': [ + 'next_hop_self', 'enabled', 'send_default_route', + ], + 'select': [ + 'as_mode', 'source_int', 'prefix_list_in', 'prefix_list_out', + 'route_map_in', 'route_map_out', + ], + 'list': ['listen_ranges'], + } + INT_VALIDATIONS = { + 'as_number': {'min': 1, 'max': 4294967295}, + } + EXIST_ATTR = 'peergroup' + SEARCH_ADDITIONAL = { + 'existing_prefixes': 'bgp.prefixlists.prefixlist', + 'existing_maps': 'bgp.routemaps.routemap', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.peergroup = {} + self.existing_prefixes = None + self.existing_maps = None + + def check(self) -> None: + self._base_check() + self._find_links() + + def _find_links(self) -> None: + links = { + 'prefix-list-in': { + 'found': False, + 'existing': self.existing_prefixes, + 'match_fields': {'name': 'prefix_list_in'} + }, + 'prefix-list-out': { + 'found': False, + 'existing': self.existing_prefixes, + 'match_fields': {'name': 'prefix_list_out'} + }, + 'route-map-in': { + 'found': False, + 'existing': self.existing_maps, + 'match_fields': {'name': 'route_map_in'} + }, + 'route-map-out': { + 'found': False, + 'existing': self.existing_maps, + 'match_fields': {'name': 'route_map_out'} + }, + } + + for key, values in links.items(): + value_name = values['match_fields']['name'] + provided = not is_unset(self.p[value_name]) + seq_uuid_mapping = {} + + if not provided: + continue + + if len(values['existing']) > 0: + for uuid, entry in values['existing'].items(): + matching = [] + + for api_field, ans_field in values['match_fields'].items(): + if not is_unset(self.p[ans_field]): + matching.append(str(entry[api_field]) == str(self.p[ans_field])) + + if all(matching): + self.p[value_name] = uuid + values['found'] = True + + if 'seqnumber' in entry: + seq_uuid_mapping[int(entry['seqnumber'])] = uuid + + if not values['found']: + self.m.fail_json( + f"Provided {key} '{value_name}' was not found!" + ) + + if len(seq_uuid_mapping) > 0: + # only the lowest prefix-list uuid is linkable - all others are just extensions of the first one + self.p[value_name] = seq_uuid_mapping[min(seq_uuid_mapping.keys())] + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if entry['prefix_list_in'] not in [None, ''] and \ + entry['prefix_list_in'] in self.existing_prefixes: + entry['prefix_list_in'] = self.existing_prefixes[entry['prefix_list_in']]['name'] + + if entry['prefix_list_out'] not in [None, ''] and \ + entry['prefix_list_out'] in self.existing_prefixes: + entry['prefix_list_out'] = self.existing_prefixes[entry['prefix_list_out']]['name'] + + if entry['route_map_in'] not in [None, ''] and \ + entry['route_map_in'] in self.existing_maps: + entry['route_map_in'] = self.existing_maps[entry['route_map_in']]['name'] + + if entry['route_map_out'] not in [None, ''] and \ + entry['route_map_out'] in self.existing_maps: + entry['route_map_out'] = self.existing_maps[entry['route_map_out']]['name'] + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_prefix_list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_prefix_list.py new file mode 100644 index 0000000..44841af --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_prefix_list.py @@ -0,0 +1,74 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Prefix(BaseModule): + CMDS = { + 'add': 'addPrefixlist', + 'del': 'delPrefixlist', + 'set': 'setPrefixlist', + 'search': 'get', + 'toggle': 'togglePrefixlist', + } + API_KEY_PATH = 'bgp.prefixlists.prefixlist' + API_MOD = 'quagga' + API_CONT = 'bgp' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['network', 'description', 'version', 'action'] + FIELDS_MATCH = ['seq', 'name'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_MATCH) + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'seq': 'seqnumber', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['version', 'action'], + } + INT_VALIDATIONS = { + 'seq': {'min': 1, 'max': 4294967294}, + } + STR_VALIDATIONS = { + 'name': r'^[a-zA-Z0-9._-]{1,64}$' + } + EXIST_ATTR = 'prefix_list' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.prefix_list = {} + self.existing_prefixes = None + self.existing_maps = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['network']) or is_unset(self.p['seq']) or is_unset(self.p['action']): + self.m.fail_json( + 'To create a BGP prefix-list you need to provide a network, ' + 'sequence-number and action!' + ) + + self._base_check(match_fields=self.FIELDS_MATCH) + + def process(self) -> None: + self.b.process() + + def get_existing(self) -> list: + return self.b.get_existing() + + def create(self) -> None: + self.b.create() + + def update(self) -> None: + self.b.update() + + def delete(self) -> None: + self.b.delete() + + def reload(self) -> None: + self.b.reload() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_redistribution.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_redistribution.py new file mode 100644 index 0000000..7cc8151 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_redistribution.py @@ -0,0 +1,96 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Redistribution(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'addRedistribution', + 'del': 'delRedistribution', + 'set': 'setRedistribution', + 'search': 'get', + 'toggle': 'toggleRedistribution', + } + API_KEY_PATH = 'bgp.redistributions.redistribution' + API_MOD = 'quagga' + API_CONT = 'bgp' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['redistribute', 'route_map'] + FIELDS_ALL = ['enabled', 'description'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'route_map': 'linkedRoutemap', + } + FIELDS_TYPING = { + 'select': ['redistribute', 'route_map',], + } + EXIST_ATTR = 'redistribute' + SEARCH_ADDITIONAL = { + 'existing_maps': 'bgp.routemaps.routemap', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.redistribute = {} + self.existing_maps = None + + def check(self) -> None: + self._base_check() + self._find_links() + + def _find_links(self) -> None: + links = { + 'route-map': { + 'found': False, + 'existing': self.existing_maps, + 'match_fields': {'name': 'route_map'} + }, + } + + for key, values in links.items(): + value_name = values['match_fields']['name'] + provided = not is_unset(self.p[value_name]) + seq_uuid_mapping = {} + + if not provided: + continue + + if len(values['existing']) > 0: + for uuid, entry in values['existing'].items(): + matching = [] + + for api_field, ans_field in values['match_fields'].items(): + if not is_unset(self.p[ans_field]): + matching.append(str(entry[api_field]) == str(self.p[ans_field])) + + if all(matching): + self.p[value_name] = uuid + values['found'] = True + + if 'seqnumber' in entry: + seq_uuid_mapping[int(entry['seqnumber'])] = uuid + + if not values['found']: + self.m.fail_json( + f"Provided {key} '{value_name}' was not found!" + ) + + if len(seq_uuid_mapping) > 0: + # only the lowest prefix-list uuid is linkable - all others are just extensions of the first one + self.p[value_name] = seq_uuid_mapping[min(seq_uuid_mapping.keys())] + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if entry['route_map'] not in [None, ''] and \ + entry['route_map'] in self.existing_maps: + entry['route_map'] = self.existing_maps[entry['route_map']]['name'] + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_route_map.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_route_map.py new file mode 100644 index 0000000..6c77dc0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_bgp_route_map.py @@ -0,0 +1,149 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class RouteMap(BaseModule): + CMDS = { + 'add': 'addRoutemap', + 'del': 'delRoutemap', + 'set': 'setRoutemap', + 'search': 'get', + 'toggle': 'toggleRoutemap', + } + API_KEY_PATH = 'bgp.routemaps.routemap' + API_MOD = 'quagga' + API_CONT = 'bgp' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'action', 'description', 'id', 'as_path_list', 'prefix_list', + 'community_list', 'set', + ] + FIELDS_ALL = ['name', 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'as_path_list': 'match', + 'prefix_list': 'match2', + 'community_list': 'match3', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'int': ['id'], + 'list': ['as_path_list', 'prefix_list', 'community_list'], + 'select': ['action'], + } + INT_VALIDATIONS = { + 'id': {'min': 10, 'max': 99}, + } + STR_VALIDATIONS = { + 'name': r'^[a-zA-Z0-9._-]{1,64}$' + } + EXIST_ATTR = 'route_map' + SEARCH_ADDITIONAL = { + 'existing_paths': 'bgp.aspaths.aspath', + 'existing_prefixes': 'bgp.prefixlists.prefixlist', + 'existing_communities': 'bgp.communitylists.communitylist', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.route_map = {} + self.existing_paths = None + self.existing_prefixes = None + self.existing_communities = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['id']) or is_unset(self.p['action']): + self.m.fail_json( + 'To create a BGP route-map you need to provide an ID and action!' + ) + + self._base_check() + + if self.p['state'] == 'present': + self._find_links() + + def _find_links(self) -> None: + self.b.find_multiple_links( + field='as_path_list', + existing=self.existing_paths, + existing_field_id='description', + ) + self.b.find_multiple_links( + field='community_list', + existing=self.existing_communities, + existing_field_id='description', + ) + + key = 'prefix_list' + if len(self.p[key]) > 0: + uuids = [] + provided_count = 0 + provided_prefixes = {} + + for k, v in self.p[key].items(): + if not isinstance(v, list): + v = [v] + + provided_count += len(v) + provided_prefixes[k] = [int(_v) for _v in v] + + if len(self.existing_prefixes) > 0: + for uuid, entry in self.existing_prefixes.items(): + if entry['name'] in provided_prefixes and \ + int(entry['seqnumber']) in provided_prefixes[entry['name']]: + uuids.append(uuid) + + if len(uuids) != provided_count: + self.m.fail_json( + 'At least one of the provided prefix-list entries was not found!' + ) + + self.p[key] = uuids + + else: + self.p[key] = [] + self.r['diff']['after'][key] = self.p[key] + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if len(entry['as_path_list']) > 0: + _list = [] + for path in entry['as_path_list']: + if path in self.existing_paths: + _list.append( + self.existing_paths[path]['description'] + ) + + entry['as_path_list'] = _list + + if len(entry['prefix_list']) > 0: + _list = [] + for pre in entry['prefix_list']: + if pre in self.existing_prefixes: + _list.append( + self.existing_prefixes[pre]['name'] + ) + + entry['prefix_list'] = _list + + if len(entry['community_list']) > 0: + _list = [] + for comm in entry['community_list']: + if comm in self.existing_communities: + _list.append( + self.existing_communities[comm]['description'] + ) + + entry['community_list'] = _list + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_general.py new file mode 100644 index 0000000..8fa440a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_general.py @@ -0,0 +1,33 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'general' + API_MOD = 'quagga' + API_CONT = 'general' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'enabled', 'profile', 'carp', 'log', 'snmp_agentx', 'log_level', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'carp': 'enablecarp', + 'log': 'enablesyslog', + 'snmp_agentx': 'enablesnmp', + 'log_level': 'sysloglevel', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'carp', 'log', 'snmp_agentx'], + 'select': ['log_level', 'profile'], + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_general.py new file mode 100644 index 0000000..0c17eab --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_general.py @@ -0,0 +1,30 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'ospf6' + API_MOD = 'quagga' + API_CONT = 'ospf6settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'carp', 'id', 'enabled', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'carp': 'carp_demote', + 'id': 'routerid', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'carp'], + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_interface.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_interface.py new file mode 100644 index 0000000..98f583b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_interface.py @@ -0,0 +1,63 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Interface(BaseModule): + CMDS = { + 'add': 'addInterface', + 'del': 'delInterface', + 'set': 'setInterface', + 'search': 'get', + 'toggle': 'toggleInterface', + } + API_KEY_PATH = 'ospf6.interfaces.interface' + API_MOD = 'quagga' + API_CONT = 'ospf6settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'interface', 'area', 'passive', 'cost', 'cost_demoted', 'carp_depend_on', + 'hello_interval', 'dead_interval', 'retransmit_interval', 'transmit_delay', + 'priority', 'network_type', + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + INT_VALIDATIONS = { + 'cost': {'min': 0, 'max': 4294967295}, + 'hello_interval': {'min': 0, 'max': 4294967295}, + 'dead_interval': {'min': 0, 'max': 4294967295}, + 'retransmit_interval': {'min': 0, 'max': 4294967295}, + 'transmit_delay': {'min': 0, 'max': 4294967295}, + 'priority': {'min': 0, 'max': 4294967295}, + 'cost_demoted': {'min': 1, 'max': 65535}, + } + FIELDS_TRANSLATE = { + 'interface': 'interfacename', + 'hello_interval': 'hellointerval', + 'dead_interval': 'deadinterval', + 'retransmit_interval': 'retransmitinterval', + 'transmit_delay': 'transmitdelay', + 'network_type': 'networktype', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'passive'], + 'select': ['interface', 'carp_depend_on', 'network_type'], + } + EXIST_ATTR = 'int' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.int = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['area']): + self.m.fail_json( + 'To create a OSPFv3 interface you need to provide its area!' + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_network.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_network.py new file mode 100644 index 0000000..cd88650 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_network.py @@ -0,0 +1,88 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip_or_network, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Network(BaseModule): + CMDS = { + 'add': 'addNetwork', + 'del': 'delNetwork', + 'set': 'setNetwork', + 'search': 'get', + 'toggle': 'toggleNetwork', + } + API_KEY_PATH = 'ospf6.networks.network' + API_MOD = 'quagga' + API_CONT = 'ospf6settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['ip', 'mask', 'area', 'area_range', 'prefix_list_in', 'prefix_list_out'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'ip': 'ipaddr', + 'mask': 'netmask', + 'area_range': 'arearange', + 'prefix_list_in': 'linkedPrefixlistIn', + 'prefix_list_out': 'linkedPrefixlistOut', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['prefix_list_in', 'prefix_list_out'], + } + INT_VALIDATIONS = { + 'mask': {'min': 0, 'max': 128}, + } + EXIST_ATTR = 'net' + SEARCH_ADDITIONAL = { + 'existing_prefixes': 'ospf6.prefixlists.prefixlist', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.net = {} + self.existing_prefixes = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['area']): + self.m.fail_json( + 'To create a OSPF network you need to provide an area!' + ) + + if not is_ip_or_network(f"{self.p['ip']}/{self.p['mask']}", strict=True): + self.m.fail_json( + 'The combination of the provided ip and network mask is invalid: ' + f"'{self.p['ip']}/{self.p['mask']}'!" + ) + + self._base_check() + + if self.p['state'] == 'present': + self.b.find_single_link( + field='prefix_list_in', + existing=self.existing_prefixes, + ) + self.b.find_single_link( + field='prefix_list_out', + existing=self.existing_prefixes, + ) + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if entry['prefix_list_in'] not in [None, ''] and \ + entry['prefix_list_in'] in self.existing_prefixes: + entry['prefix_list_in'] = self.existing_prefixes[entry['prefix_list_in']]['name'] + + if entry['prefix_list_out'] not in [None, ''] and \ + entry['prefix_list_out'] in self.existing_prefixes: + entry['prefix_list_out'] = self.existing_prefixes[entry['prefix_list_out']]['name'] + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_prefix_list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_prefix_list.py new file mode 100644 index 0000000..285c39d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_prefix_list.py @@ -0,0 +1,50 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Prefix(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addPrefixlist', + 'del': 'delPrefixlist', + 'set': 'setPrefixlist', + 'search': 'get', + 'toggle': 'togglePrefixlist', + } + API_KEY_PATH = 'ospf6.prefixlists.prefixlist' + API_MOD = 'quagga' + API_CONT = 'ospf6settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['seq', 'action', 'network'] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + INT_VALIDATIONS = { + 'seq': {'min': 10, 'max': 99}, + } + FIELDS_TRANSLATE = { + 'seq': 'seqnumber', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + } + EXIST_ATTR = 'prefix' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.prefix = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['seq']) or is_unset(self.p['action']) or is_unset(self.p['network']): + self.m.fail_json( + 'To create a OSPF prefix-list you need to provide its sequence-number, ' + 'action and network!' + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_redistribution.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_redistribution.py new file mode 100644 index 0000000..f50a1c8 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_redistribution.py @@ -0,0 +1,96 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Redistribution(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'addRedistribution', + 'del': 'delRedistribution', + 'set': 'setRedistribution', + 'search': 'get', + 'toggle': 'toggleRedistribution', + } + API_KEY_PATH = 'ospf6.redistributions.redistribution' + API_MOD = 'quagga' + API_CONT = 'ospf6settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['redistribute', 'route_map'] + FIELDS_ALL = ['enabled', 'description'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'route_map': 'linkedRoutemap', + } + FIELDS_TYPING = { + 'select': ['redistribute', 'route_map',], + } + EXIST_ATTR = 'redistribute' + SEARCH_ADDITIONAL = { + 'existing_maps': 'ospf6.routemaps.routemap', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.redistribute = {} + self.existing_maps = None + + def check(self) -> None: + self._base_check() + self._find_links() + + def _find_links(self) -> None: + links = { + 'route-map': { + 'found': False, + 'existing': self.existing_maps, + 'match_fields': {'name': 'route_map'} + }, + } + + for key, values in links.items(): + value_name = values['match_fields']['name'] + provided = not is_unset(self.p[value_name]) + seq_uuid_mapping = {} + + if not provided: + continue + + if len(values['existing']) > 0: + for uuid, entry in values['existing'].items(): + matching = [] + + for api_field, ans_field in values['match_fields'].items(): + if not is_unset(self.p[ans_field]): + matching.append(str(entry[api_field]) == str(self.p[ans_field])) + + if all(matching): + self.p[value_name] = uuid + values['found'] = True + + if 'seqnumber' in entry: + seq_uuid_mapping[int(entry['seqnumber'])] = uuid + + if not values['found']: + self.m.fail_json( + f"Provided {key} '{value_name}' was not found!" + ) + + if len(seq_uuid_mapping) > 0: + # only the lowest prefix-list uuid is linkable - all others are just extensions of the first one + self.p[value_name] = seq_uuid_mapping[min(seq_uuid_mapping.keys())] + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if entry['route_map'] not in [None, ''] and \ + entry['route_map'] in self.existing_maps: + entry['route_map'] = self.existing_maps[entry['route_map']]['name'] + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_route_map.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_route_map.py new file mode 100644 index 0000000..d6b36bf --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf3_route_map.py @@ -0,0 +1,79 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class RouteMap(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addRoutemap', + 'del': 'delRoutemap', + 'set': 'setRoutemap', + 'search': 'get', + 'toggle': 'toggleRoutemap', + } + API_KEY_PATH = 'ospf6.routemaps.routemap' + API_MOD = 'quagga' + API_CONT = 'ospf6settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['action', 'id', 'prefix_list', 'set'] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'prefix_list': 'match2', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['prefix_list'], + 'select': ['action'], + 'int': ['id'], + } + INT_VALIDATIONS = { + 'id': {'min': 10, 'max': 99}, + } + EXIST_ATTR = 'route_map' + SEARCH_ADDITIONAL = { + 'existing_prefixes': 'ospf6.prefixlists.prefixlist', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.route_map = {} + self.existing_prefixes = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['id']) or is_unset(self.p['action']): + self.m.fail_json( + 'To create a OSPF route-map you need to provide an ID and action!' + ) + + self._base_check() + + if self.p['state'] == 'present': + self.b.find_multiple_links( + field='prefix_list', + existing=self.existing_prefixes, + ) + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if len(entry['prefix_list']) > 0: + _list = [] + for pre in entry['prefix_list']: + if pre in self.existing_prefixes: + _list.append( + self.existing_prefixes[pre]['name'] + ) + + entry['prefix_list'] = _list + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_general.py new file mode 100644 index 0000000..1fd2dd7 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_general.py @@ -0,0 +1,41 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'ospf' + API_MOD = 'quagga' + API_CONT = 'ospfsettings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'carp', 'id', 'cost', 'enabled', 'passive_ints', + 'originate', 'originate_always', 'originate_metric', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'carp': 'carp_demote', + 'id': 'routerid', + 'cost': 'costreference', + 'originate_always': 'originatealways', + 'originate_metric': 'originatemetric', + 'passive_ints': 'passiveinterfaces', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'carp', 'originate', 'originate_always'], + 'list': ['passive_ints'], + 'int': ['originate_metric', 'cost'], + } + INT_VALIDATIONS = { + 'cost': {'min': 1, 'max': 4294967}, + 'originate_metric': {'min': 0, 'max': 16777214}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_interface.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_interface.py new file mode 100644 index 0000000..9da13f7 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_interface.py @@ -0,0 +1,72 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Interface(BaseModule): + CMDS = { + 'add': 'addInterface', + 'del': 'delInterface', + 'set': 'setInterface', + 'search': 'get', + 'toggle': 'toggleInterface', + } + API_KEY_PATH = 'ospf.interfaces.interface' + API_MOD = 'quagga' + API_CONT = 'ospfsettings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'interface', 'area', 'auth_type', 'auth_key', 'auth_key_id', 'cost', + 'hello_interval', 'dead_interval', 'retransmit_interval', 'transmit_delay', + 'priority', 'network_type', 'carp_depend_on', 'cost_demoted', + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + INT_VALIDATIONS = { + 'cost': {'min': 1, 'max': 65535}, + 'hello_interval': {'min': 0, 'max': 4294967295}, + 'dead_interval': {'min': 0, 'max': 4294967295}, + 'retransmit_interval': {'min': 0, 'max': 4294967295}, + 'transmit_delay': {'min': 0, 'max': 4294967295}, + 'priority': {'min': 0, 'max': 4294967295}, + 'cost_demoted': {'min': 1, 'max': 65535}, + 'auth_key_id': {'min': 1, 'max': 255}, + } + FIELDS_TRANSLATE = { + 'interface': 'interfacename', + 'hello_interval': 'hellointerval', + 'dead_interval': 'deadinterval', + 'retransmit_interval': 'retransmitinterval', + 'transmit_delay': 'transmitdelay', + 'network_type': 'networktype', + 'auth_type': 'authtype', + 'auth_key': 'authkey', + 'auth_key_id': 'authkey_id', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['interface', 'carp_depend_on', 'network_type', 'auth_type'], + } + EXIST_ATTR = 'int' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.int = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['area']): + self.m.fail_json( + 'To create a OSPF interface you need to provide its area!' + ) + + if not is_unset(self.p['auth_type']) and is_unset(self.p['auth_key']): + self.m.fail_json( + 'You need to provide an authentication-key if you enable authentication!' + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_network.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_network.py new file mode 100644 index 0000000..36c99b5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_network.py @@ -0,0 +1,88 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip_or_network, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Network(BaseModule): + CMDS = { + 'add': 'addNetwork', + 'del': 'delNetwork', + 'set': 'setNetwork', + 'search': 'get', + 'toggle': 'toggleNetwork', + } + API_KEY_PATH = 'ospf.networks.network' + API_MOD = 'quagga' + API_CONT = 'ospfsettings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['ip', 'mask', 'area', 'area_range', 'prefix_list_in', 'prefix_list_out'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'ip': 'ipaddr', + 'mask': 'netmask', + 'area_range': 'arearange', + 'prefix_list_in': 'linkedPrefixlistIn', + 'prefix_list_out': 'linkedPrefixlistOut', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['prefix_list_in', 'prefix_list_out'], + } + INT_VALIDATIONS = { + 'mask': {'min': 0, 'max': 32}, + } + EXIST_ATTR = 'net' + SEARCH_ADDITIONAL = { + 'existing_prefixes': 'ospf.prefixlists.prefixlist', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.net = {} + self.existing_prefixes = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['area']): + self.m.fail_json( + 'To create a OSPF network you need to provide an area!' + ) + + if not is_ip_or_network(f"{self.p['ip']}/{self.p['mask']}", strict=True): + self.m.fail_json( + 'The combination of the provided ip and network mask is invalid: ' + f"'{self.p['ip']}/{self.p['mask']}'!" + ) + + self._base_check() + + if self.p['state'] == 'present': + self.b.find_single_link( + field='prefix_list_in', + existing=self.existing_prefixes, + ) + self.b.find_single_link( + field='prefix_list_out', + existing=self.existing_prefixes, + ) + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if entry['prefix_list_in'] not in [None, ''] and \ + entry['prefix_list_in'] in self.existing_prefixes: + entry['prefix_list_in'] = self.existing_prefixes[entry['prefix_list_in']]['name'] + + if entry['prefix_list_out'] not in [None, ''] and \ + entry['prefix_list_out'] in self.existing_prefixes: + entry['prefix_list_out'] = self.existing_prefixes[entry['prefix_list_out']]['name'] + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_prefix_list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_prefix_list.py new file mode 100644 index 0000000..34d0d92 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_prefix_list.py @@ -0,0 +1,50 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Prefix(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addPrefixlist', + 'del': 'delPrefixlist', + 'set': 'setPrefixlist', + 'search': 'get', + 'toggle': 'togglePrefixlist', + } + API_KEY_PATH = 'ospf.prefixlists.prefixlist' + API_MOD = 'quagga' + API_CONT = 'ospfsettings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['seq', 'action', 'network'] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + INT_VALIDATIONS = { + 'seq': {'min': 10, 'max': 99}, + } + FIELDS_TRANSLATE = { + 'seq': 'seqnumber', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + } + EXIST_ATTR = 'prefix' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.prefix = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['seq']) or is_unset(self.p['action']) or is_unset(self.p['network']): + self.m.fail_json( + 'To create a OSPF prefix-list you need to provide its sequence-number, ' + 'action and network!' + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_redistribution.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_redistribution.py new file mode 100644 index 0000000..4833b14 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_redistribution.py @@ -0,0 +1,96 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Redistribution(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'addRedistribution', + 'del': 'delRedistribution', + 'set': 'setRedistribution', + 'search': 'get', + 'toggle': 'toggleRedistribution', + } + API_KEY_PATH = 'ospf.redistributions.redistribution' + API_MOD = 'quagga' + API_CONT = 'ospfsettings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['redistribute', 'route_map'] + FIELDS_ALL = ['enabled', 'description'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'route_map': 'linkedRoutemap', + } + FIELDS_TYPING = { + 'select': ['redistribute', 'route_map',], + } + EXIST_ATTR = 'redistribute' + SEARCH_ADDITIONAL = { + 'existing_maps': 'ospf.routemaps.routemap', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.redistribute = {} + self.existing_maps = None + + def check(self) -> None: + self._base_check() + self._find_links() + + def _find_links(self) -> None: + links = { + 'route-map': { + 'found': False, + 'existing': self.existing_maps, + 'match_fields': {'name': 'route_map'} + }, + } + + for key, values in links.items(): + value_name = values['match_fields']['name'] + provided = not is_unset(self.p[value_name]) + seq_uuid_mapping = {} + + if not provided: + continue + + if len(values['existing']) > 0: + for uuid, entry in values['existing'].items(): + matching = [] + + for api_field, ans_field in values['match_fields'].items(): + if not is_unset(self.p[ans_field]): + matching.append(str(entry[api_field]) == str(self.p[ans_field])) + + if all(matching): + self.p[value_name] = uuid + values['found'] = True + + if 'seqnumber' in entry: + seq_uuid_mapping[int(entry['seqnumber'])] = uuid + + if not values['found']: + self.m.fail_json( + f"Provided {key} '{value_name}' was not found!" + ) + + if len(seq_uuid_mapping) > 0: + # only the lowest prefix-list uuid is linkable - all others are just extensions of the first one + self.p[value_name] = seq_uuid_mapping[min(seq_uuid_mapping.keys())] + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if entry['route_map'] not in [None, ''] and \ + entry['route_map'] in self.existing_maps: + entry['route_map'] = self.existing_maps[entry['route_map']]['name'] + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_route_map.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_route_map.py new file mode 100644 index 0000000..5d4587b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_ospf_route_map.py @@ -0,0 +1,79 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class RouteMap(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addRoutemap', + 'del': 'delRoutemap', + 'set': 'setRoutemap', + 'search': 'get', + 'toggle': 'toggleRoutemap', + } + API_KEY_PATH = 'ospf.routemaps.routemap' + API_MOD = 'quagga' + API_CONT = 'ospfsettings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['action', 'id', 'prefix_list', 'set'] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'prefix_list': 'match2', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['prefix_list'], + 'select': ['action'], + 'int': ['id'], + } + INT_VALIDATIONS = { + 'id': {'min': 10, 'max': 99}, + } + EXIST_ATTR = 'route_map' + SEARCH_ADDITIONAL = { + 'existing_prefixes': 'ospf.prefixlists.prefixlist', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.route_map = {} + self.existing_prefixes = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['id']) or is_unset(self.p['action']): + self.m.fail_json( + 'To create a OSPF route-map you need to provide an ID and action!' + ) + + self._base_check() + + if self.p['state'] == 'present': + self.b.find_multiple_links( + field='prefix_list', + existing=self.existing_prefixes, + ) + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + if len(entry['prefix_list']) > 0: + _list = [] + for pre in entry['prefix_list']: + if pre in self.existing_prefixes: + _list.append( + self.existing_prefixes[pre]['name'] + ) + + entry['prefix_list'] = _list + + existing.append(entry) + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_rip.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_rip.py new file mode 100644 index 0000000..7830f08 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/frr_rip.py @@ -0,0 +1,37 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class Rip(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'rip' + API_MOD = 'quagga' + API_CONT = 'rip' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'version', 'metric', 'passive_ints', 'enabled', 'networks', + 'redistribute', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'passive_ints': 'passiveinterfaces', + 'metric': 'defaultmetric', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['passive_ints', 'networks', 'redistribute'], + 'int': ['version'], + } + INT_VALIDATIONS = { + 'version': {'min': 1, 'max': 2}, + 'metric': {'min': 1, 'max': 16}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/gateway.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/gateway.py new file mode 100644 index 0000000..745d1c6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/gateway.py @@ -0,0 +1,94 @@ +from ipaddress import ip_address + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip4, is_ip6 +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Gw(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_gateway', + 'del': 'del_gateway', + 'set': 'set_gateway', + 'search': 'search_gateway', + 'detail': 'get_gateway', + 'toggle': 'toggle_gateway', + } + API_KEY_PATH = 'gateway_item' + API_MOD = 'routing' + API_CONT = 'settings' + FIELDS_CHANGE = [ + 'name', 'interface', 'gateway', 'default_gw', 'far_gw', 'monitor_disable', 'monitor_noroute', 'monitor', + 'force_down', 'priority', 'weight', 'latency_low', 'latency_high', 'loss_low', 'loss_high', 'interval', + 'time_period', 'loss_interval', 'data_length', 'description', 'ip_protocol', + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_BOOL_INVERT = ['enabled'] + FIELDS_TRANSLATE = { + 'description': 'descr', + 'ip_protocol': 'ipprotocol', + 'enabled': 'disabled', + 'default_gw': 'defaultgw', + 'far_gw': 'fargw', + 'latency_low': 'latencylow', + 'latency_high': 'latencyhigh', + 'loss_low': 'losslow', + 'loss_high': 'losshigh', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'default_gw', 'far_gw', 'monitor_disable', 'monitor_noroute', 'force_down'], + 'int': [ + 'priority', 'weight', 'latency_low', 'latency_high', 'loss_low', 'loss_high', 'interval', 'time_period', + 'loss_interval', 'data_length', + ], + 'select': ['interface', 'ip_protocol'], + } + INT_VALIDATIONS = { + 'priority': {'min': 0, 'max': 255}, + 'weight': {'min': 1, 'max': 5}, + 'latency_low': {'min': 1, 'max': 9999}, + 'latency_high': {'min': 1, 'max': 9999}, + 'loss_low': {'min': 1, 'max': 99}, + 'loss_high': {'min': 1, 'max': 99}, + 'interval': {'min': 1, 'max': 9999}, + 'time_period': {'min': 1, 'max': 9999}, + 'data_length': {'min': 0, 'max': 9999}, + } + EXIST_ATTR = 'gw' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.gw = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if not is_unset(self.p['gateway']): + try: + ip_address(self.p['gateway']) + + except ValueError: + self.m.fail_json(f"Value '{self.p['gateway']}' is not a valid gateway!") + + if self.p['ip_protocol'] == 'inet' and not is_ip4(self.p['gateway']): + self.m.fail_json(f"Gateway '{self.p['gateway']}' is not a valid IPv4-address!") + elif self.p['ip_protocol'] == 'inet6' and not is_ip6(self.p['gateway']): + self.m.fail_json(f"Gateway '{self.p['gateway']}' is not a valid IPv6-address!") + + if self.p['monitor']: + try: + ip_address(self.p['monitor']) + + except ValueError: + self.m.fail_json(f"Value '{self.p['monitor']}' is not a valid monitor address!") + + if not self.p['interface']: + self.m.fail_json('You need to provide a value for the interface!') + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/group.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/group.py new file mode 100644 index 0000000..e21ec70 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/group.py @@ -0,0 +1,58 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset, get_key_by_value_from_selection +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Group(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'set', + 'search': 'search', + 'detail': 'get', + } + API_KEY_PATH = 'group' + API_MOD = 'auth' + API_CONT = 'group' + FIELDS_CHANGE = ['description', 'source_net', 'privilege', 'member'] + FIELDS_TYPING = { + 'list': ['privilege', 'source_net'], + 'list_value': ['member'], + } + FIELDS_TRANSLATE = { + 'privilege': 'priv', + 'source_net': 'source_networks', + } + FIELDS_ALL = ['name'] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'group' + STR_VALIDATIONS = { + 'name': r'^[a-zA-Z0-9._-]{1,32}$' + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.group = {} + + def delete(self) -> None: + if self.group['scope'] == 'system': + self.m.fail_json(f"Not allowed to delete system group {self.group['name']}") + + self.b.delete() + + def _build_request(self) -> dict: + raw_request = self.b.build_request() + + if not is_unset(self.p['member']): + # translate user-names to user-id's + raw_request[self.API_KEY_PATH]['member'] = self.b.RESP_JOIN_CHAR.join([ + get_key_by_value_from_selection(self.b.raw['member'], m) + for m in self.p['member'] + ]) + + return raw_request diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_acl.py new file mode 100644 index 0000000..74a2d24 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_acl.py @@ -0,0 +1,126 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import is_unset + + +class HaproxyAcl(BaseModule): + FIELD_ID = 'name' + + CMDS = { + 'add': 'addAcl', + 'del': 'delAcl', + 'set': 'setAcl', + 'search': 'get', + 'toggle': 'toggleAcl', + } + API_KEY_PATH = 'haproxy.acls.acl' + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + 'case_sensitive': 'caseSensitive', + 'allowed_users': 'allowedUsers', + 'allowed_groups': 'allowedGroups', + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + [ + 'name', 'description', 'expression', 'negate', + 'hdr_beg', 'hdr_end', 'hdr', 'hdr_reg', 'hdr_sub', + 'path_beg', 'path_end', 'path', 'path_reg', 'path_dir', 'path_sub', + 'cust_hdr_beg_name', 'cust_hdr_beg', 'cust_hdr_end_name', 'cust_hdr_end', + 'cust_hdr_name', 'cust_hdr', 'cust_hdr_reg_name', 'cust_hdr_reg', + 'cust_hdr_sub_name', 'cust_hdr_sub', 'url_param', 'url_param_value', + 'ssl_c_verify_code', 'ssl_c_ca_commonname', 'ssl_hello_type', + 'src', 'src_port', 'src_port_comparison', 'nbsrv', 'nbsrv_backend', + 'ssl_fc_sni', 'ssl_sni', 'ssl_sni_sub', 'ssl_sni_beg', + 'ssl_sni_end', 'ssl_sni_reg', 'custom_acl', + 'src_bytes_in_rate_comparison', 'src_bytes_in_rate', + 'src_bytes_out_rate_comparison', 'src_bytes_out_rate', + 'src_conn_cnt_comparison', 'src_conn_cnt', + 'src_conn_cur_comparison', 'src_conn_cur', + 'src_conn_rate_comparison', 'src_conn_rate', + 'src_http_err_cnt_comparison', 'src_http_err_cnt', + 'src_http_err_rate_comparison', 'src_http_err_rate', + 'src_http_req_cnt_comparison', 'src_http_req_cnt', + 'src_http_req_rate_comparison', 'src_http_req_rate', + 'src_kbytes_in_comparison', 'src_kbytes_in', + 'src_kbytes_out_comparison', 'src_kbytes_out', + 'src_sess_cnt_comparison', 'src_sess_cnt', + 'src_sess_rate_comparison', 'src_sess_rate' + ] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'str': ['name', 'description', 'hdr_beg', 'hdr_end', 'hdr', 'hdr_reg', 'hdr_sub', + 'path_beg', 'path_end', 'path', 'path_reg', 'path_dir', 'path_sub', + 'cust_hdr_beg_name', 'cust_hdr_beg', 'cust_hdr_end_name', 'cust_hdr_end', + 'cust_hdr_name', 'cust_hdr', 'cust_hdr_reg_name', 'cust_hdr_reg', + 'cust_hdr_sub_name', 'cust_hdr_sub', 'url_param', 'url_param_value', + 'ssl_c_ca_commonname', 'src', 'ssl_fc_sni', 'ssl_sni', 'ssl_sni_sub', + 'ssl_sni_beg', 'ssl_sni_end', 'ssl_sni_reg', 'custom_acl'], + 'bool': ['negate', 'case_sensitive'], + 'int': ['ssl_c_verify_code', 'src_port', 'nbsrv', 'src_bytes_in_rate', 'src_bytes_out_rate', + 'src_conn_cnt', 'src_conn_cur', 'src_conn_rate', 'src_http_err_cnt', 'src_http_err_rate', + 'src_http_req_cnt', 'src_http_req_rate', 'src_kbytes_in', 'src_kbytes_out', + 'src_sess_cnt', 'src_sess_rate'], + 'list': ['allowed_users', 'allowed_groups'], + 'select': ['expression', 'ssl_hello_type', 'nbsrv_backend', 'src_port_comparison', + 'src_bytes_in_rate_comparison', 'src_bytes_out_rate_comparison', 'src_conn_cnt_comparison', + 'src_conn_cur_comparison', 'src_conn_rate_comparison', 'src_http_err_cnt_comparison', + 'src_http_err_rate_comparison', 'src_http_req_cnt_comparison', 'src_http_req_rate_comparison', + 'src_kbytes_in_comparison', 'src_kbytes_out_comparison', 'src_sess_cnt_comparison', + 'src_sess_rate_comparison'] + } + + EXIST_ATTR = 'haproxy_acl' + + STR_VALIDATIONS = { + 'name': r'^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$', + } + + ### TODO : Uncomment backends when implemented + + SEARCH_ADDITIONAL = { + 'existing_users': 'haproxy.users.user', + 'existing_groups': 'haproxy.groups.group', + # 'existing_backends': 'haproxy.backends.backend', + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.haproxy_acl = {} + self.existing_users = {} + self.existing_groups = {} + # self.existing_backends = {} + + def check(self) -> None: + self._base_check() + + if self.p['state'] == 'present': + if is_unset(self.p['expression']): + self.m.fail_json("You need to provide an 'expression' to create an ACL!") + + if self.p.get('allowed_users'): + self.b.find_multiple_links( + field='allowed_users', + existing=self.existing_users, + existing_field_id='name', + ) + if self.p.get('allowed_groups'): + self.b.find_multiple_links( + field='allowed_groups', + existing=self.existing_groups, + existing_field_id='name', + ) + # if self.p.get('nbsrv_backend'): + # self.b.find_single_link( + # field='nbsrv_backend', + # existing=self.existing_backends, + # existing_field_id='name', + # ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_action.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_action.py new file mode 100644 index 0000000..52913ea --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_action.py @@ -0,0 +1,187 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class HaproxyAction(BaseModule): + FIELD_ID = 'name' + + CMDS = { + 'add': 'addAction', + 'del': 'delAction', + 'set': 'setAction', + 'search': 'get', + 'toggle': 'toggleAction', + } + API_KEY_PATH = 'haproxy.actions.action' + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + 'test_type': 'testType', + 'linked_acls': 'linkedAcls', + 'custom_rule': 'custom', + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + [ + 'name', 'description', 'operator', 'type', + 'use_backend', 'use_server', 'fcgi_pass_header', 'fcgi_set_param', + 'http_request_auth', 'http_request_redirect', 'http_request_lua', + 'http_request_use_service', 'http_request_add_header_name', + 'http_request_add_header_content', 'http_request_set_header_name', + 'http_request_set_header_content', 'http_request_del_header_name', + 'http_request_replace_header_name', 'http_request_replace_header_regex', + 'http_request_replace_value_name', 'http_request_replace_value_regex', + 'http_request_set_path', 'http_request_set_var_scope', + 'http_request_set_var_name', 'http_request_set_var_expr', + 'http_response_lua', 'http_response_add_header_name', + 'http_response_add_header_content', 'http_response_set_header_name', + 'http_response_set_header_content', 'http_response_del_header_name', + 'http_response_replace_header_name', 'http_response_replace_header_regex', + 'http_response_replace_value_name', 'http_response_replace_value_regex', + 'http_response_set_status_code', 'http_response_set_status_reason', + 'http_response_set_var_scope', 'http_response_set_var_name', + 'http_response_set_var_expr', 'monitor_fail_uri', + 'tcp_request_content_lua', 'tcp_request_content_use_service', + 'tcp_request_inspect_delay', 'tcp_response_content_lua', + 'tcp_response_inspect_delay', + 'map_use_backend_file', 'map_use_backend_default' + ] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + "str": [ + "name", + "description", + "fcgi_pass_header", + "fcgi_set_param", + "http_request_auth", + "http_request_redirect", + "http_request_use_service", + "http_request_add_header_name", + "http_request_add_header_content", + "http_request_set_header_name", + "http_request_set_header_content", + "http_request_del_header_name", + "http_request_replace_header_name", + "http_request_replace_header_regex", + "http_request_replace_value_name", + "http_request_replace_value_regex", + "http_request_set_path", + "http_request_set_var_name", + "http_request_set_var_expr", + "http_response_add_header_name", + "http_response_add_header_content", + "http_response_set_header_name", + "http_response_set_header_content", + "http_response_del_header_name", + "http_response_replace_header_name", + "http_response_replace_header_regex", + "http_response_replace_value_name", + "http_response_replace_value_regex", + "http_response_set_status_reason", + "http_response_set_var_name", + "http_response_set_var_expr", + "monitor_fail_uri", + "tcp_request_content_use_service", + "tcp_request_inspect_delay", + "tcp_response_inspect_delay", + "custom_rule", + ], + "int": ["http_response_set_status_code"], + "list": ["linked_acls"], + "select": [ + "test_type", + "operator", + "type", + "http_request_set_var_scope", + "http_response_set_var_scope", + "use_backend", + "use_server", + "http_request_lua", + "http_response_lua", + "tcp_request_content_lua", + "tcp_response_content_lua", + "map_use_backend_file", + "map_use_backend_default", + ], + } + + EXIST_ATTR = 'haproxy_action' + + STR_VALIDATIONS = { + 'name': r'^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$', + } + + ### TODO : Uncomment backends, servers, and mapping files when implemented + + SEARCH_ADDITIONAL = { + 'existing_acls': 'haproxy.acls.acl', + # 'existing_backends': 'haproxy.backends.backend', + # 'existing_servers': 'haproxy.servers.server', + 'existing_luas': 'haproxy.luas.lua', + # 'existing_mapfiles': 'haproxy.mapfiles.mapfile', + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.haproxy_action = {} + self.existing_acls = {} + # self.existing_backends = {} + # self.existing_servers = {} + self.existing_luas = {} + # self.existing_mapfiles = {} + + def check(self) -> None: + self._base_check() + if self.p['state'] == 'present': + if self.p.get('linked_acls'): + self.b.find_multiple_links( + field='linked_acls', + existing=self.existing_acls, + ) + # if self.p.get('use_backend'): + # self.b.find_single_link( + # field='use_backend', + # existing=self.existing_backends, + # ) + # if self.p.get('use_server'): + # self.b.find_single_link( + # field='use_server', + # existing=self.existing_servers, + # ) + if self.p.get('http_request_lua'): + self.b.find_single_link( + field='http_request_lua', + existing=self.existing_luas, + ) + if self.p.get('http_response_lua'): + self.b.find_single_link( + field='http_response_lua', + existing=self.existing_luas, + ) + if self.p.get('tcp_request_content_lua'): + self.b.find_single_link( + field='tcp_request_content_lua', + existing=self.existing_luas, + ) + if self.p.get('tcp_response_content_lua'): + self.b.find_single_link( + field='tcp_response_content_lua', + existing=self.existing_luas, + ) + # if self.p.get('map_use_backend_file'): + # self.b.find_single_link( + # field='map_use_backend_file', + # existing=self.existing_mapfiles, + # ) + # if self.p.get('map_use_backend_default'): + # self.b.find_single_link( + # field='map_use_backend_default', + # existing=self.existing_backends, + # ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_cpu.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_cpu.py new file mode 100644 index 0000000..29f4f9a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_cpu.py @@ -0,0 +1,42 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class HaproxyCpu(BaseModule): + FIELD_ID = 'name' + + CMDS = { + 'add': 'addCpu', + 'del': 'delCpu', + 'set': 'setCpu', + 'search': 'get', + 'toggle': 'toggleCpu', + } + API_KEY_PATH = 'haproxy.cpus.cpu' + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_CHANGE = ['enabled', 'name', 'thread_id', 'cpu_id'] + FIELDS_ALL = FIELDS_CHANGE + + EXIST_ATTR = 'cpu' + + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['thread_id'], + 'list': ['cpu_id'], + } + + STR_VALIDATIONS = { + 'name': r'^[0-9a-zA-Z._-]{1,255}$', # Name validation from XML model + } + + TIMEOUT = 20.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.cpu = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_errorfile.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_errorfile.py new file mode 100644 index 0000000..c5eedf2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_errorfile.py @@ -0,0 +1,40 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class HaproxyErrorfile(BaseModule): + FIELD_ID = 'name' + + CMDS = { + 'add': 'addErrorfile', + 'del': 'delErrorfile', + 'set': 'setErrorfile', + 'search': 'get', + 'toggle': 'toggleErrorfile', + } + API_KEY_PATH = 'haproxy.errorfiles.errorfile' + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_CHANGE = ['name', 'description', 'code', 'content'] + FIELDS_ALL = FIELDS_CHANGE + + EXIST_ATTR = 'haproxy_errorfile' + + FIELDS_TYPING = { + 'select': ['code'], + } + + STR_VALIDATIONS = { + 'name': r'^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$', + } + + TIMEOUT = 20.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.haproxy_errorfile = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_fcgi.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_fcgi.py new file mode 100644 index 0000000..b683f55 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_fcgi.py @@ -0,0 +1,65 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class HaproxyFcgi(BaseModule): + FIELD_ID = 'name' + + CMDS = { + 'add': 'addFcgi', + 'del': 'delFcgi', + 'set': 'setFcgi', + 'search': 'get', + 'toggle': 'toggleFcgi', + } + API_KEY_PATH = 'haproxy.fcgis.fcgi' + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + 'linked_actions': 'linkedActions' + } + + FIELDS_CHANGE = ['enabled', 'name', 'description', 'docroot', 'index', 'path_info', + 'log_stderr', 'keep_conn', 'get_values', 'mpxs_conns', 'max_reqs', 'linked_actions'] + FIELDS_ALL = FIELDS_CHANGE + + EXIST_ATTR = 'haproxy_fcgi' + + FIELDS_TYPING = { + 'bool': ['enabled', 'log_stderr', 'keep_conn', 'get_values', 'mpxs_conns'], + 'int': ['max_reqs'], + 'list': ['linked_actions'] + } + + STR_VALIDATIONS = { + 'name': r'^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$', + } + + INT_VALIDATIONS = { + 'max_reqs': {'min': 1, 'max': 100000}, + } + + SEARCH_ADDITIONAL = { + 'existing_actions': 'haproxy.actions.action', + } + + TIMEOUT = 20.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.haproxy_fcgi = {} + self.existing_actions = {} + + def check(self) -> None: + self._base_check() + + if self.p['state'] == 'present': + self.b.find_multiple_links( + field='linked_actions', + existing=self.existing_actions, + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_cache.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_cache.py new file mode 100644 index 0000000..c20c5e3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_cache.py @@ -0,0 +1,45 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class HaproxyGeneralCache(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'haproxy.general.cache' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + 'total_max_size': 'totalMaxSize', + 'max_age': 'maxAge', + 'max_object_size': 'maxObjectSize', + 'process_vary': 'processVary', + 'max_secondary_entries': 'maxSecondaryEntries', + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + ['enabled'] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'bool': ['enabled', 'process_vary'], + 'int': ['total_max_size', 'max_age', 'max_object_size', 'max_secondary_entries'], + } + + INT_VALIDATIONS = { + 'total_max_size': {'min': 1, 'max': 4095}, + 'max_age': {'min': 1, 'max': 3600}, + 'max_object_size': {'min': 1, 'max': 2146435072}, + 'max_secondary_entries': {'min': 1}, + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_defaults.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_defaults.py new file mode 100644 index 0000000..e2e2d05 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_defaults.py @@ -0,0 +1,48 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class HaproxyGeneralDefaults(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'haproxy.general.defaults' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + 'max_connections': 'maxConnections', + 'max_connections_servers': 'maxConnectionsServers', + 'timeout_client': 'timeoutClient', + 'timeout_connect': 'timeoutConnect', + 'timeout_check': 'timeoutCheck', + 'timeout_server': 'timeoutServer', + 'custom_options': 'customOptions' + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + ['retries', 'redispatch', 'init_addr' ] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'int': ['max_connections', 'max_connections_servers', 'retries'], + 'select': ['redispatch'], + 'list': ['init_addr'], + } + + INT_VALIDATIONS = { + 'max_connections': {'min': 0, 'max': 10000000}, + 'max_connections_servers': {'min': 0, 'max': 10000000}, + 'retries': {'min': 0, 'max': 100}, + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + \ No newline at end of file diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_logging.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_logging.py new file mode 100644 index 0000000..781441d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_logging.py @@ -0,0 +1,35 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class HaproxyGeneralLogging(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'haproxy.general.logging' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_CHANGE = ['host', 'facility', 'level', 'length'] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'select': ['facility', 'level'], + 'int': ['length'], + } + + INT_VALIDATIONS = { + 'length': {'min': 64, 'max': 65535}, + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + \ No newline at end of file diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_peers.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_peers.py new file mode 100644 index 0000000..0d02c9f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_peers.py @@ -0,0 +1,35 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class HaproxyGeneralPeers(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'haproxy.general.peers' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_CHANGE = ['enabled', 'name1', 'listen1', 'port1', 'name2', 'listen2', 'port2'] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'bool': ['enabled'], + 'int': ['port1', 'port2'], + } + + INT_VALIDATIONS = { + 'port1': {'min': 1, 'max': 65535}, + 'port2': {'min': 1, 'max': 65535}, + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_settings.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_settings.py new file mode 100644 index 0000000..8e6b0bd --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_settings.py @@ -0,0 +1,45 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class HaproxyGeneralSettings(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'haproxy.general' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + 'graceful_stop': 'gracefulStop', + 'hard_stop_after': 'hardStopAfter', + 'close_spread_time': 'closeSpreadTime', + 'seamless_reload': 'seamlessReload', + 'show_intro': 'showIntro', + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + ['enabled'] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'bool': ['enabled', 'graceful_stop', 'seamless_reload', 'show_intro'], + 'int': ['hard_stop_after', 'close_spread_time'], + } + + FIELDS_BOOL_INVERT = ['graceful_stop'] + + INT_VALIDATIONS = { + 'hard_stop_after': {'min': 0, 'max': 86400}, # 0 to 24 hours in seconds + 'close_spread_time': {'min': 0, 'max': 3600}, # 0 to 1 hour in seconds + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_stats.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_stats.py new file mode 100644 index 0000000..c8147b5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_stats.py @@ -0,0 +1,66 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class HaproxyGeneralStats(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'haproxy.general.stats' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + 'remote_enabled': 'remoteEnabled', + 'remote_bind': 'remoteBind', + 'auth_enabled': 'authEnabled', + 'allowed_users': 'allowedUsers', + 'allowed_groups': 'allowedGroups', + 'custom_options': 'customOptions' + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + FIELDS_CHANGE += ['enabled', 'port', 'users', 'prometheus_enabled', 'prometheus_bind', 'prometheus_path'] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'bool': ['enabled', 'remote_enabled', 'auth_enabled', 'prometheus_enabled'], + 'int': ['port'], + 'list': ['remote_bind', 'users', 'allowed_users', 'allowed_groups', 'prometheus_bind'], + } + + INT_VALIDATIONS = { + 'port': {'min': 1024, 'max': 65535}, + } + + SEARCH_ADDITIONAL = { + 'existing_users': 'haproxy.users.user', + 'existing_groups': 'haproxy.groups.group', + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + self.existing_users = {} + self.existing_groups = {} + + def check(self) -> None: + self._base_check() + + self.b.find_multiple_links( + field='allowed_users', + existing=self.existing_users, + existing_field_id='name', + ) + self.b.find_multiple_links( + field='allowed_groups', + existing=self.existing_groups, + existing_field_id='name', + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_tuning.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_tuning.py new file mode 100644 index 0000000..a073657 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_general_tuning.py @@ -0,0 +1,80 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class HaproxyGeneralTuning(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'haproxy.general.tuning' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + 'max_connections': 'maxConnections', + 'resolvers_prefer': 'resolversPrefer', + 'ssl_server_verify': 'sslServerVerify', + 'max_dh_size': 'maxDHSize', + 'buffer_size': 'bufferSize', + 'spread_checks': 'spreadChecks', + 'bogus_proxy_enabled': 'bogusProxyEnabled', + 'lua_max_mem': 'luaMaxMem', + 'custom_options': 'customOptions', + 'ocsp_update_enabled': 'ocspUpdateEnabled', + 'ocsp_update_min_delay': 'ocspUpdateMinDelay', + 'ocsp_update_max_delay': 'ocspUpdateMaxDelay', + 'ssl_defaults_enabled': 'ssl_defaultsEnabled', + 'ssl_bind_options': 'ssl_bindOptions', + 'ssl_min_version': 'ssl_minVersion', + 'ssl_max_version': 'ssl_maxVersion', + 'ssl_cipher_list': 'ssl_cipherList', + 'ssl_cipher_suites': 'ssl_cipherSuites', + 'h2_initial_window_size': 'h2_initialWindowSize', + 'h2_initial_window_size_outgoing': 'h2_initialWindowSizeOutgoing', + 'h2_initial_window_size_incoming': 'h2_initialWindowSizeIncoming', + 'h2_max_concurrent_streams': 'h2_maxConcurrentStreams', + 'h2_max_concurrent_streams_outgoing': 'h2_maxConcurrentStreamsOutgoing', + 'h2_max_concurrent_streams_incoming': 'h2_maxConcurrentStreamsIncoming', + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + ['root', 'nbthread'] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'bool': ['root', 'bogus_proxy_enabled', 'ocsp_update_enabled', 'ssl_defaults_enabled'], + 'int': ['max_connections', 'nbthread', 'max_dh_size', 'buffer_size', 'spread_checks', + 'lua_max_mem', 'ocsp_update_min_delay', 'ocsp_update_max_delay', + 'h2_initial_window_size', 'h2_initial_window_size_outgoing', + 'h2_initial_window_size_incoming', 'h2_max_concurrent_streams', + 'h2_max_concurrent_streams_outgoing', 'h2_max_concurrent_streams_incoming'], + 'select': ['resolvers_prefer', 'ssl_server_verify', 'ssl_min_version', 'ssl_max_version'], + 'list': ['ssl_bind_options'], + } + + INT_VALIDATIONS = { + 'max_connections': {'min': 0, 'max': 10000000}, + 'nbthread': {'min': 1, 'max': 1024}, + 'max_dh_size': {'min': 1024, 'max': 16384}, + 'buffer_size': {'min': 1024, 'max': 1048576}, + 'spread_checks': {'min': 0, 'max': 50}, + 'lua_max_mem': {'min': 0, 'max': 1024}, + 'ocsp_update_min_delay': {'min': 1, 'max': 86400}, + 'ocsp_update_max_delay': {'min': 1, 'max': 86400}, + 'h2_initial_window_size': {'min': 0, 'max': 10000000}, + 'h2_initial_window_size_outgoing': {'min': 0, 'max': 10000000}, + 'h2_initial_window_size_incoming': {'min': 0, 'max': 10000000}, + 'h2_max_concurrent_streams': {'min': 0, 'max': 10000000}, + 'h2_max_concurrent_streams_outgoing': {'min': 0, 'max': 10000000}, + 'h2_max_concurrent_streams_incoming': {'min': 0, 'max': 10000000}, + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_group.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_group.py new file mode 100644 index 0000000..7f22e74 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_group.py @@ -0,0 +1,52 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class HaproxyGroup(BaseModule): + FIELD_ID = 'name' + + CMDS = { + 'add': 'addGroup', + 'del': 'delGroup', + 'set': 'setGroup', + 'search': 'get', + 'toggle': 'toggleGroup', + } + API_KEY_PATH = 'haproxy.groups.group' + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_CHANGE = ['enabled', 'name', 'description', 'members', 'add_userlist'] + FIELDS_ALL = FIELDS_CHANGE + + EXIST_ATTR = 'haproxy_group' + + FIELDS_TYPING = { + 'bool': ['enabled', 'add_userlist'], + 'list': ['members'] + } + + SEARCH_ADDITIONAL = { + 'existing_users': 'haproxy.users.user', + } + + TIMEOUT = 20.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.haproxy_group = {} + self.existing_users = {} + + def check(self) -> None: + self._base_check() + + if self.p['state'] == 'present': + self.b.find_multiple_links( + field='members', + existing=self.existing_users, + existing_field_id='name', + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_lua.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_lua.py new file mode 100644 index 0000000..d4dd98f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_lua.py @@ -0,0 +1,41 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class HaproxyLua(BaseModule): + FIELD_ID = 'name' + + CMDS = { + 'add': 'addLua', + 'del': 'delLua', + 'set': 'setLua', + 'search': 'get', + 'toggle': 'toggleLua', + } + API_KEY_PATH = 'haproxy.luas.lua' + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_CHANGE = ['enabled', 'name', 'description', 'preload', 'filename_scheme', 'content'] + FIELDS_ALL = FIELDS_CHANGE + + EXIST_ATTR = 'haproxy_lua' + + FIELDS_TYPING = { + 'bool': ['enabled', 'preload'], + 'select': ['filename_scheme'], + } + + STR_VALIDATIONS = { + 'name': r'^[^\t^,^;^\.^\[^\]^\{^\}]{1,255}$', + } + + TIMEOUT = 20.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.haproxy_lua = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_maintenance.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_maintenance.py new file mode 100644 index 0000000..0ccea5c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_maintenance.py @@ -0,0 +1,33 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class HaproxyMaintenance(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'haproxy.maintenance.cronjobs' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'haproxy' + API_CONT = 'settings' + + FIELDS_TRANSLATE = { + 'sync_certs': 'syncCerts', + 'reload_service': 'reloadService', + 'restart_service': 'restartService', + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_TYPING = { + 'bool': ['sync_certs', 'reload_service', 'restart_service'], + } + + TIMEOUT = 20.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_user.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_user.py new file mode 100644 index 0000000..f74ebea --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/haproxy_user.py @@ -0,0 +1,38 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class HaproxyUser(BaseModule): + FIELD_ID = 'name' + + CMDS = { + 'add': 'addUser', + 'del': 'delUser', + 'set': 'setUser', + 'search': 'get', + 'toggle': 'toggleUser', + } + API_KEY_PATH = 'haproxy.users.user' + API_MOD = 'haproxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_CHANGE = ['enabled', 'name', 'description', 'password'] + FIELDS_ALL = FIELDS_CHANGE + + FIELDS_DIFF_NO_LOG = ['password'] + + EXIST_ATTR = 'haproxy_user' + + FIELDS_TYPING = { + 'bool': ['enabled'], + } + + TIMEOUT = 20.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.haproxy_user = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/hasync_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/hasync_general.py new file mode 100644 index 0000000..42a0fce --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/hasync_general.py @@ -0,0 +1,46 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'hasync' + API_MOD = 'core' + API_CONT = 'hasync' + FIELDS_CHANGE = [ + 'preempt', 'disconnect_ppps', 'pfsync_interface', 'pfsync_peer_ip', 'pfsync_version', 'synchronize_to_ip', + 'verify_peer', 'username', 'syncitems', + ] + FIELDS_ALL = ['password'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'preempt': 'disablepreempt', + 'disconnect_ppps': 'disconnectppps', + 'pfsync_interface': 'pfsyncinterface', + 'pfsync_peer_ip': 'pfsyncpeerip', + 'pfsync_version': 'pfsyncversion', + 'synchronize_to_ip': 'synchronizetoip', + 'verify_peer': 'verifypeer', + } + FIELDS_BOOL_INVERT = ['preempt'] + FIELDS_TYPING = { + 'bool': ['preempt', 'disconnect_ppps', 'verify_peer'], + 'select': ['pfsync_interface', 'pfsync_version'], + 'list': ['syncitems'], + } + FIELDS_DIFF_NO_LOG = ['password'] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + + def check(self) -> None: + if self.p['update_password'] == 'always': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['password'] + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_general.py new file mode 100644 index 0000000..34c77d0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_general.py @@ -0,0 +1,137 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_selected, get_key_by_value_from_selection +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip_or_network, is_unset + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY = 'general' + API_KEY_1 = 'ids' + API_KEY_PATH = f'{API_KEY_1}.{API_KEY}' + API_MOD = 'ids' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'block', 'promiscuous', 'enabled', 'interfaces', 'pattern_matcher', 'local_networks', 'default_packet_size', + 'syslog_alerts', 'syslog_output', 'log_level', 'log_rotate', 'log_retention', 'log_payload', + 'profile', 'profile_toclient_groups', 'profile_toserver_groups', 'schedule', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'block': 'ips', + 'promiscuous': 'promisc', + 'syslog_alerts': 'syslog', + 'syslog_output': 'syslog_eve', + 'log_level': 'verbosity', + 'pattern_matcher': 'MPMAlgo', + 'local_networks': 'homenet', + 'default_packet_size': 'defaultPacketSize', + 'log_rotate': 'AlertLogrotate', + 'log_retention': 'AlertSaveLogs', + 'log_payload': 'LogPayload', + 'schedule': 'UpdateCron', + } + FIELDS_TRANSLATE_SPECIAL = { + 'profile': 'Profile', + 'profile_toclient_groups': 'toclient_groups', + 'profile_toserver_groups': 'toserver_groups', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'block', 'promiscuous', 'syslog_alerts', 'syslog_output', 'log_payload'], + 'int': ['default_packet_size', 'log_retention'], + 'list': ['local_networks', 'interfaces'], + 'select': ['log_level', 'pattern_matcher', 'log_rotate', 'schedule'], + } + FIELDS_IGNORE = ['detect'] + INT_VALIDATIONS = { + 'log_retention': {'min': 1, 'max': 1000}, + 'profile_toclient_groups': {'min': 1, 'max': 65535}, + 'profile_toserver_groups': {'min': 1, 'max': 65535}, + 'default_packet_size': {'min': 82, 'max': 65535}, + } + FIELDS_VALUE_MAPPING = { + 'log_rotate': { + 'weekly': 'W0D23', + 'daily': 'D0', + }, + 'log_level': { + 'info': 'v', + 'perf': 'vv', + 'config': 'vvv', + 'debug': 'vvvv', + }, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + + def check(self) -> None: + if len(self.p['interfaces']) == 0: + self.m.fail_json("You need to supply 'interfaces'!") + + if self.p['profile'] == 'custom' and ( + is_unset(self.p['profile_toclient_groups']) or is_unset(self.p['profile_toserver_groups']) + ): + self.m.fail_json( + "You need to supply 'profile_toclient_groups' and 'profile_toserver_groups' " + "when using the profile 'custom'!" + ) + + for net in self.p['local_networks']: + if not is_ip_or_network(net): + self.m.fail_json( + f"It seems you provided an invalid network in 'local_networks': '{net}'" + ) + + self._base_check() + + def _search_call(self) -> dict: + settings = self.s.get(cnf={ + **self.call_cnf, **{'command': self.CMDS['search']} + })[self.API_KEY_1][self.API_KEY] + + simple = self.b.simplify_existing(settings) + + try: + # resolve schedule/cron name to uuid + self.p['schedule'] = get_key_by_value_from_selection( + selection=settings[self.FIELDS_TRANSLATE['schedule']], + value=self.p['schedule'], + ) + + except KeyError: + # list module not supplying params + pass + + simple['profile'] = get_selected( + settings['detect'][self.FIELDS_TRANSLATE_SPECIAL['profile']] + ) + simple['profile_toclient_groups'] = settings['detect'][self.FIELDS_TRANSLATE_SPECIAL['profile_toclient_groups']] + simple['profile_toserver_groups'] = settings['detect'][self.FIELDS_TRANSLATE_SPECIAL['profile_toserver_groups']] + + for field in self.FIELDS_IGNORE: + if field in simple: + simple.pop(field) + + return simple + + def _build_request(self) -> dict: + raw_request = self.b.build_request( + ignore_fields=['profile', 'profile_toclient_groups', 'profile_toserver_groups'] + ) + raw_request[self.API_KEY]['detect'] = { + self.FIELDS_TRANSLATE_SPECIAL['profile']: self.p['profile'], + self.FIELDS_TRANSLATE_SPECIAL['profile_toclient_groups']: self.p['profile_toclient_groups'], + self.FIELDS_TRANSLATE_SPECIAL['profile_toserver_groups']: self.p['profile_toserver_groups'], + } + + return {self.API_KEY_1: raw_request} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_policy.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_policy.py new file mode 100644 index 0000000..3d77bdb --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_policy.py @@ -0,0 +1,175 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_ruleset import Ruleset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset, is_true, ensure_list + + +class Policy(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_policy', + 'del': 'del_policy', + 'set': 'set_policy', + 'search': 'search_policy', + 'detail': 'get_policy', + 'toggle': 'toggle_policy', + } + API_KEY = 'policy' + API_KEY_PATH = f'policies.{API_KEY}' + API_MOD = 'ids' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['priority', 'action', 'rulesets', 'new_action'] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'priority': 'prio', + } + FIELDS_TRANSLATE_SPECIAL = { + 'rules': 'content', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['new_action'], + 'list': ['rulesets', 'action'], + 'int': ['priority'], + } + EXIST_ATTR = 'policy' + QUERY_MAX_RULES = 5000 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.policy = {} + self.exists = False + self.enabled_rulesets = {} + self.ruleset_names = {} + + def check(self) -> None: + self._search_call() + if self.p['state'] == 'present' and not is_unset(self.p['rulesets']): + if len(self.enabled_rulesets) == 0: + self._search_rulesets() + + if len(self.enabled_rulesets) == 0: + self.m.fail_json("You need to enable rulesets before referencing them!") + + ruleset_uuids = [] + for ruleset in self.p['rulesets']: + found = False + for enabled_ruleset, uuid in self.enabled_rulesets.items(): + if enabled_ruleset == ruleset: + found = True + ruleset_uuids.append(uuid) + + if not found: + self.m.fail_json( + f"The ruleset '{ruleset}' was not found! " + "You need to enable a ruleset before referencing it. " + f"Enabled ones are: {list(self.enabled_rulesets.keys())}" + ) + + ruleset_uuids.sort() + self.p['rulesets'] = ruleset_uuids + + self.r['diff']['after'] = self.b.build_diff(data=self.p) + + def get_existing(self) -> list: + return self._search_call() + + def _search_call(self) -> list: + # NOTE: workaround for issue with incomplete response-data from 'get' endpoint: + # https://github.com/opnsense/core/issues/7094 + existing = self.s.post(cnf={ + **self.call_cnf, + 'command': self.CMDS['search'], + 'data': {'current': 1, 'rowCount': self.QUERY_MAX_RULES, 'sort': self.FIELD_ID, 'searchPhrase': ''}, + })['rows'] + + if self.FIELD_ID in self.p: # list module + for policy in existing: + if policy[self.FIELD_ID] == self.p[self.FIELD_ID]: + self.exists = True + self.call_cnf['params'] = [policy['uuid']] + raw_policy = self.s.get(cnf={ + **self.call_cnf, + 'command': self.CMDS['detail'], + })[self.API_KEY] + self.policy['rules'] = self._parse_rules(raw_policy) + self.policy = self.b.simplify_existing(raw_policy) + self.enabled_rulesets = self._format_ruleset(raw_policy['rulesets']) + self.policy['uuid'] = policy['uuid'] + if 'content' in self.policy: + self.policy.pop('content') + + self.r['diff']['before'] = self.policy + + return existing + + @staticmethod + def _parse_rules(raw_policy: dict) -> dict: + parsed = {} + + if 'content' not in raw_policy: + return parsed + + for key_value, values in raw_policy['content'].items(): + if is_true(values['selected']): + key, value = key_value.split('.', 1) + if key in parsed: + parsed[key].append(value) + else: + parsed[key] = [value] + + return parsed + + def _build_request(self) -> dict: + raw_request = self.b.build_request(ignore_fields=['rules']) + + # formatting dynamic rules + # example: 'policy_content_affected_product: "affected_product.Adobe_Flash,affected_product.Adobe_Reader"' + raw_request_rules = {} + raw_request_content = [] + for key, values in self.p['rules'].items(): + fmt_values = [f'{key}.{value}' for value in ensure_list(values)] + raw_request_rules[f'policy_content_{key}'] = self.b.RESP_JOIN_CHAR.join(fmt_values) + raw_request_content.extend(fmt_values) + + raw_request[self.API_KEY]['content'] = self.b.RESP_JOIN_CHAR.join(raw_request_content) + + return { + **raw_request, + **raw_request_rules, + } + + def _search_rulesets(self): + # check if any ruleset is enabled before creating a new policy + self.enabled_rulesets = self._format_ruleset( + self.s.get(cnf={ + **self.call_cnf, + 'command': self.CMDS['detail'], + })[self.API_KEY]['rulesets'] + ) + + def _search_ruleset_names(self): + ruleset_details = self.s.get(cnf={ + **self.call_cnf, + 'command': Ruleset.CMDS['search'], + })['rows'] + + for ruleset in ruleset_details: + self.ruleset_names[ruleset[Ruleset.FIELD_PK]] = ruleset[Ruleset.FIELD_ID] + + def _format_ruleset(self, rulesets: dict) -> dict: + if len(self.ruleset_names) == 0: + self._search_ruleset_names() + + formatted = {} + + for uuid, ruleset in rulesets.items(): + formatted[self.ruleset_names[ruleset['value']]] = uuid + + return formatted diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_policy_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_policy_rule.py new file mode 100644 index 0000000..708b2fd --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_policy_rule.py @@ -0,0 +1,65 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_true + + +class Rule(BaseModule): + FIELD_ID = 'sid' + CMDS = { + 'add': 'add_policy_rule', + 'del': 'del_policy_rule', + 'set': 'set_policy_rule', + 'search': 'search_policy_rule', + 'detail': 'get_policy_rule', + 'toggle': 'toggle_policy_rule', + } + API_KEY_PATH = 'policies.rule' + API_MOD = 'ids' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['action'] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + 'int': ['sid'], + } + EXIST_ATTR = 'rule' + QUERY_MAX_RULES = 1000 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.rule = {} + self.exists = False + + def check(self) -> None: + self.r['diff'] = {'before': {}, 'after': {}} + self._search_call() + self.r['diff']['after'] = self.b.build_diff(data=self.p) + self.r['changed'] = self.r['diff']['before'] != self.r['diff']['after'] + + def _search_call(self) -> list: + existing = self.s.post(cnf={ + **self.call_cnf, + 'command': self.CMDS['search'], + 'data': {'current': 1, 'rowCount': self.QUERY_MAX_RULES, 'sort': self.FIELD_ID}, + })['rows'] + + if self.FIELD_ID in self.p: # list module + for rule in existing: + if int(rule[self.FIELD_ID]) == self.p[self.FIELD_ID]: + self.exists = True + self.rule['uuid'] = rule['uuid'] + self.call_cnf['params'] = [self.rule['uuid']] + self.rule[self.FIELD_ID] = int(rule[self.FIELD_ID]) + self.rule['enabled'] = is_true(rule['enabled']) + self.rule['action'] = rule['action'].lower() + self.r['diff']['before'] = self.rule + break + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_rule.py new file mode 100644 index 0000000..69506e6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_rule.py @@ -0,0 +1,90 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + to_digit + + +class Rule(BaseModule): + FIELD_PK = 'sid' + CMDS = { + 'set': 'setRule', + 'search': 'searchinstalledrules', + 'toggle': 'toggleRule', + } + API_KEY_PATH = 'rules.rule' + API_MOD = 'ids' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reloadRules' + FIELDS_CHANGE = ['action'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + 'int': ['sid'], + } + EXIST_ATTR = 'rule' + QUERY_MAX_RULES = 5000 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.rule = {} + self.exists = False + + def check(self) -> None: + self._search_call() + if not self.exists: + self.m.fail_json(f"The provided rule '{self.p[self.FIELD_PK]}' was not found!") + + self.r['diff']['after'] = self.b.build_diff(data=self.p) + self.r['changed'] = self.r['diff']['before'] != self.r['diff']['after'] + + def process(self) -> None: + if self.rule['action'] != self.p['action']: + self.update() + + if self.rule['enabled'] != self.p['enabled']: + self.toggle() + + def _search_call(self) -> list: + # NOTE: workaround for issue with incomplete response-data from 'get' endpoint: + # https://github.com/opnsense/core/issues/7094 + existing = self.s.post(cnf={ + **self.call_cnf, + 'command': self.CMDS['search'], + 'data': {'current': 1, 'rowCount': self.QUERY_MAX_RULES, 'sort': self.FIELD_PK}, + })['rows'] + + if self.FIELD_PK in self.p: # list module + for rule in existing: + if rule[self.FIELD_PK] == self.p[self.FIELD_PK]: + self.exists = True + self.rule[self.FIELD_PK] = rule[self.FIELD_PK] + self.rule['enabled'] = rule['status'] == 'enabled' + self.rule['action'] = rule['action'] + self.r['diff']['before'] = self.rule + + return existing + + def toggle(self) -> None: + if not self.m.check_mode: + self.s.post(cnf={ + **self.call_cnf, **{ + 'command': self.CMDS['toggle'], + 'params': [self.p[self.FIELD_PK], to_digit(self.p['enabled'])], + } + }) + + def update(self) -> None: + if not self.m.check_mode: + self.s.post(cnf={ + **self.call_cnf, **{ + 'command': self.CMDS['set'], + 'params': [self.p[self.FIELD_PK]], + 'data': {'action': self.p['action']} + } + }) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_ruleset.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_ruleset.py new file mode 100644 index 0000000..dd5aa6c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_ruleset.py @@ -0,0 +1,87 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_true, to_digit + + +class Ruleset(BaseModule): + FIELD_PK = 'filename' + FIELD_ID = 'description' + CMDS = { + 'set': 'set_ruleset', + 'search': 'list_rulesets', + 'toggle': 'toggle_ruleset', + } + API_KEY_PATH = 'rulesets.ruleset' + API_MOD = 'ids' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'updateRules' + FIELDS_CHANGE = ['enabled'] + FIELDS_COPY = [FIELD_PK, 'documentation_url'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_COPY) + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + } + EXIST_ATTR = 'ruleset' + QUERY_MAX_RULES = 1000 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.ruleset = {} + self.exists = False + self.existing_rulesets_desc = [] + + def check(self) -> None: + self._search_call() + if not self.exists: + self.m.fail_json( + f"The provided ruleset '{self.p[self.FIELD_ID]}' was not found! " + f"Available ones are: '{self.existing_rulesets_desc}'" + ) + + self.r['diff']['after'] = self.b.build_diff(data=self.p) + self.r['changed'] = self.r['diff']['before'] != self.r['diff']['after'] + + def process(self) -> None: + if self.r['changed']: + self.toggle() + + def _search_call(self) -> list: + # NOTE: workaround for issue with incomplete response-data from 'get' endpoint: + # https://github.com/opnsense/core/issues/7094 + existing = self.s.post(cnf={ + **self.call_cnf, + 'command': self.CMDS['search'], + 'data': {'current': 1, 'rowCount': self.QUERY_MAX_RULES, 'sort': self.FIELD_PK, 'searchPhrase': ''}, + })['rows'] + + if self.FIELD_ID in self.p: # list module + for ruleset in existing: + self.existing_rulesets_desc.append(ruleset[self.FIELD_ID]) + + if ruleset[self.FIELD_ID] == self.p[self.FIELD_ID]: + self.exists = True + for field in self.FIELDS_COPY: + self.ruleset[field] = ruleset[field] + self.p[field] = ruleset[field] + + self.ruleset[self.FIELD_ID] = ruleset[self.FIELD_ID] + self.ruleset['enabled'] = is_true(ruleset['enabled']) + self.r['diff']['before'] = self.ruleset + + return existing + + def toggle(self) -> None: + if not self.m.check_mode: + self.s.post(cnf={ + **self.call_cnf, **{ + 'command': self.CMDS['toggle'], + 'params': [self.p[self.FIELD_PK], to_digit(self.p['enabled'])], + } + }) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_user_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_user_rule.py new file mode 100644 index 0000000..cabd4de --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ids_user_rule.py @@ -0,0 +1,72 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Rule(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_user_rule', + 'set': 'set_user_rule', + 'del': 'del_user_rule', + 'search': 'search_user_rule', + 'detail': 'get_user_rule', + 'toggle': 'toggle_user_rule', + } + API_KEY = 'rule' + API_KEY_PATH = f'userDefinedRules.{API_KEY}' + API_MOD = 'ids' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reloadRules' + FIELDS_CHANGE = ['source_ip', 'destination_ip', 'ssl_fingerprint', 'action', 'bypass'] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'source_ip': 'source', + 'destination_ip': 'destination', + 'ssl_fingerprint': 'fingerprint', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'bypass'], + 'select': ['action'], + } + EXIST_ATTR = 'rule' + QUERY_MAX_RULES = 5000 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.rule = {} + self.exists = False + + def check(self): + self._search_call() + self.r['diff']['after'] = self.b.build_diff(data=self.p) + + def get_existing(self) -> list: + return self._search_call() + + def _search_call(self) -> list: + existing = self.s.post(cnf={ + **self.call_cnf, + 'command': self.CMDS['search'], + 'data': {'current': 1, 'rowCount': self.QUERY_MAX_RULES, 'sort': self.FIELD_ID}, + })['rows'] + + if self.FIELD_ID in self.p: # list module + for rule in existing: + if rule[self.FIELD_ID] == self.p[self.FIELD_ID]: + self.exists = True + self.call_cnf['params'] = [rule['uuid']] + self.rule = self.b.simplify_existing( + self.s.get(cnf={ + **self.call_cnf, + 'command': self.CMDS['detail'], + })[self.API_KEY] + ) + self.rule['uuid'] = rule['uuid'] + self.r['diff']['before'] = self.rule + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_bridge.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_bridge.py new file mode 100644 index 0000000..6f28879 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_bridge.py @@ -0,0 +1,65 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Bridge(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + 'toggle': 'toggleItem', + } + API_KEY_PATH = 'bridge.bridged' + API_KEY_PATH_REQ = 'bridge' + API_MOD = 'interfaces' + API_CONT = 'bridge_settings' + FIELDS_CHANGE = [ + 'members', 'link_local', 'stp', 'stp_proto', 'stp_interfaces', 'stp_max_age', 'stp_fwdelay', 'stp_hold', + 'cache_size', 'cache_timeout', 'span_interfaces', 'edge_interfaces', 'auto_edge_interfaces', + 'ptp_interfaces', 'auto_ptp_interfaces', 'static_interfaces', 'private_interfaces', + ] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'description': 'descr', + 'link_local': 'linklocal', + 'stp': 'enablestp', + 'stp_proto': 'proto', + 'stp_interfaces': 'stp', + 'stp_max_age': 'maxage', + 'stp_fwdelay': 'fwdelay', + 'stp_hold': 'holdcnt', + 'cache_size': 'maxaddr', + 'cache_timeout': 'timeout', + 'span_interfaces': 'span', + 'edge_interfaces': 'edge', + 'auto_edge_interfaces': 'autoedge', + 'ptp_interfaces': 'ptp', + 'auto_ptp_interfaces': 'autoptp', + 'static_interfaces': 'static', + 'private_interfaces': 'private', + } + FIELDS_TYPING = { + 'bool': ['link_local', 'stp'], + 'list': [ + 'members', 'stp_interfaces', 'span_interfaces', 'edge_interfaces', 'auto_edge_interfaces', + 'ptp_interfaces', 'auto_ptp_interfaces', 'static_interfaces', 'private_interfaces', + ], + 'select': ['stp_proto'], + 'int': ['stp_max_age', 'stp_fwdelay', 'stp_hold', 'cache_size', 'cache_timeout'], + } + INT_VALIDATIONS = { + 'stp_max_age': {'min': 6, 'max': 40}, + 'stp_fwdelay': {'min': 4, 'max': 30}, + 'stp_hold': {'min': 1, 'max': 10}, + } + EXIST_ATTR = 'bridge' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.bridge = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_gif.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_gif.py new file mode 100644 index 0000000..cf398a4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_gif.py @@ -0,0 +1,49 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Gif(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + 'toggle': 'toggleItem', + } + API_KEY_PATH = 'gif.gif' + API_MOD = 'interfaces' + API_CONT = 'gif_settings' + FIELDS_CHANGE = [ + 'local', 'remote', 'tunnel_local', 'tunnel_remote', 'tunnel_remote_net', 'ingress_filtering', 'ecn_friendly', + ] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'description': 'descr', + 'local': 'local-addr', + 'remote': 'remote-addr', + 'tunnel_local': 'tunnel-local-addr', + 'tunnel_remote': 'tunnel-remote-addr', + 'tunnel_remote_net': 'tunnel-remote-net', + 'ingress_filtering': 'link2', + 'ecn_friendly': 'link1', + } + FIELDS_BOOL_INVERT = ['ingress_filtering'] + FIELDS_TYPING = { + 'bool': ['ingress_filtering', 'ecn_friendly'], + 'list': [], + 'select': [], + 'int': ['tunnel_remote_net'], + } + INT_VALIDATIONS = { + 'tunnel_remote_net': {'min': 1, 'max': 128}, + } + EXIST_ATTR = 'gif' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.gif = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_gre.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_gre.py new file mode 100644 index 0000000..92f7741 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_gre.py @@ -0,0 +1,44 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Gre(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + 'toggle': 'toggleItem', + } + API_KEY_PATH = 'gre.gre' + API_MOD = 'interfaces' + API_CONT = 'gre_settings' + FIELDS_CHANGE = ['local', 'remote', 'tunnel_local', 'tunnel_remote', 'tunnel_remote_net'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'description': 'descr', + 'local': 'local-addr', + 'remote': 'remote-addr', + 'tunnel_local': 'tunnel-local-addr', + 'tunnel_remote': 'tunnel-remote-addr', + 'tunnel_remote_net': 'tunnel-remote-net', + } + FIELDS_TYPING = { + 'bool': [], + 'list': [], + 'select': [], + 'int': ['tunnel_remote_net'], + } + INT_VALIDATIONS = { + 'tunnel_remote_net': {'min': 1, 'max': 128}, + } + EXIST_ATTR = 'gre' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.gre = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_lagg.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_lagg.py new file mode 100644 index 0000000..2c53716 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_lagg.py @@ -0,0 +1,56 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Lagg(BaseModule): + FIELD_ID = 'device' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + } + API_KEY_PATH = 'lagg.lagg' + API_MOD = 'interfaces' + API_CONT = 'lagg_settings' + FIELDS_CHANGE = [ + 'members', 'primary_member', 'proto', 'lacp_fast_timeout', 'use_flowid', + 'lagghash', 'lacp_strict', 'mtu', 'description' + ] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'device': 'laggif', + 'description': 'descr', + } + FIELDS_TYPING = { + 'bool': ['lacp_fast_timeout'], + 'list': ['members', 'lagghash'], + 'select': ['members', 'primary_member', 'proto', 'use_flowid', 'lagghash', 'lacp_strict'], + } + INT_VALIDATIONS = { + 'mtu': {'min': 576, 'max': 65535}, + } + EXIST_ATTR = 'lagg' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.lagg = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['members']): + self.m.fail_json("You need to provide a list of 'members' to create a lagg!") + + if self.p['proto'] in ['lacp', 'loadbalance'] and is_unset(self.p['lagghash']): + self.m.fail_json("You need to provide a list of 'lagghash' to create a lagg!") + + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_loopback.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_loopback.py new file mode 100644 index 0000000..70dff6d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_loopback.py @@ -0,0 +1,29 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Loopback(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + } + API_KEY_PATH = 'loopback.loopback' + API_MOD = 'interfaces' + API_CONT = 'loopback_settings' + FIELDS_CHANGE = [] + FIELDS_ALL = [FIELD_ID] + FIELDS_TYPING = {} + EXIST_ATTR = 'interface' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None,): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.interface = {} + + def check(self) -> None: + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vip.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vip.py new file mode 100644 index 0000000..aa43a40 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vip.py @@ -0,0 +1,73 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Vip(BaseModule): + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + } + API_KEY_PATH = 'vip.vip' + API_MOD = 'interfaces' + API_CONT = 'vip_settings' + FIELDS_CHANGE = [ + 'address', 'mode', 'expand', 'bind', 'gateway', 'password', 'vhid', + 'advertising_base', 'advertising_skew', 'description', 'interface', + 'peer', 'peer6', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'address': 'network', + 'expand': 'noexpand', + 'bind': 'nobind', + 'advertising_base': 'advbase', + 'advertising_skew': 'advskew', + 'description': 'descr', + } + FIELDS_DIFF_NO_LOG = ['password'] + FIELDS_BOOL_INVERT = ['expand', 'bind'] + FIELDS_TYPING = { + 'bool': ['expand', 'bind'], + 'select': ['mode', 'interface', 'vhid', 'advertising_base', 'advertising_skew'], + 'int': ['vhid', 'advertising_base', 'advertising_skew'], + } + INT_VALIDATIONS = { + 'vhid': {'min': 1, 'max': 255}, + 'advertising_base': {'min': 1, 'max': 254}, + 'advertising_skew': {'min': 0, 'max': 254}, + } + EXIST_ATTR = 'vip' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.vip = {} + + def check(self) -> None: + if self.p['address'].find('/') == -1: + self.m.fail_json('The address needs to include a subnet CIDR!') + + self.existing_entries = self.get_existing() + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) + + # NOTE: workaround for OPNsense handling 'get' differently than 'add' and 'set' + # https://github.com/opnsense/core/issues/7041 + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + entry['address'] = f"{entry['subnet']}/{entry['subnet_bits']}" + entry.pop('subnet') + entry.pop('subnet_bits') + existing.append(entry) + for field in self.FIELDS_BOOL_INVERT: + entry[field] = not entry[field] + + return existing diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vlan.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vlan.py new file mode 100644 index 0000000..f865783 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vlan.py @@ -0,0 +1,59 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Vlan(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + } + API_KEY_PATH = 'vlan.vlan' + API_MOD = 'interfaces' + API_CONT = 'vlan_settings' + FIELDS_CHANGE = ['interface', 'vlan', 'priority', 'device'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'device': 'vlanif', + 'interface': 'if', + 'vlan': 'tag', + 'priority': 'pcp', + 'description': 'descr', + } + FIELDS_TYPING = { + 'select': ['interface', 'priority'], + 'int': ['vlan', 'priority'], + } + INT_VALIDATIONS = { + 'vlan': {'min': 1, 'max': 4096}, + 'priority': {'min': 0, 'max': 7}, + } + EXIST_ATTR = 'vlan' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.vlan = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['interface']): + self.m.fail_json("You need to provide an 'interface' to create a vlan!") + + if is_unset(self.p['vlan']): + self.m.fail_json("You need to provide a 'vlan' to create a vlan-interface!") + + if is_unset(self.p['device']): + self.p['device'] = f"vlan0.{self.p['vlan']}" # OPNsense forces us to start with 'vlan0' for some reason + + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vxlan.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vxlan.py new file mode 100644 index 0000000..1e35553 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/interface_vxlan.py @@ -0,0 +1,64 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Vxlan(BaseModule): + FIELD_ID = 'id' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + } + API_KEY_PATH = 'vxlan.vxlan' + API_MOD = 'interfaces' + API_CONT = 'vxlan_settings' + FIELDS_CHANGE = ['interface', 'local', 'local_port', 'remote', 'remote_port', 'group'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + # 'name': 'deviceId', # can't be configured + 'id': 'vxlanid', + 'local': 'vxlanlocal', + 'local_port': 'vxlanlocalport', + 'remote': 'vxlanremote', + 'remote_port': 'vxlanremoteport', + 'group': 'vxlangroup', + 'interface': 'vxlandev', + } + FIELDS_TYPING = { + 'select': ['interface'], + 'int': ['id', 'local_port', 'remote_port'], + } + INT_VALIDATIONS = { + 'id': {'min': 0, 'max': 16777215}, + 'local_port': {'min': 1, 'max': 65535}, + 'remote_port': {'min': 1, 'max': 65535}, + } + FIELDS_IP = ['local', 'remote', 'group'] + EXIST_ATTR = 'vxlan' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.vxlan = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['local']): + self.m.fail_json("You need to provide a 'local' ip to create a vxlan!") + + for field in self.FIELDS_IP: + if not is_unset(self.p[field]) and not is_ip(self.p[field]): + self.m.fail_json( + f"Value '{self.p[field]}' is not a valid IP-address!" + ) + + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth.py new file mode 100644 index 0000000..01f017c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth.py @@ -0,0 +1,154 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + ModuleSoftError + + +class BaseAuth(BaseModule): + FIELD_ID = 'name' + API_MOD = 'ipsec' + API_CONT = 'connections' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'connection', 'round', 'authentication', 'id', 'eap_id', 'certificates', + 'public_keys', + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + 'authentication': 'auth', + 'public_keys': 'pubkeys', + 'certificates': 'certs', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['certificates', 'public_keys'], + 'select': ['connection', 'authentication', 'certificates', 'public_keys'], + 'int': ['round'], + } + STR_LEN_VALIDATIONS = { + 'id': {'min': 0, 'max': 1024}, + 'eap_id': {'min': 0, 'max': 1024}, + } + INT_VALIDATIONS = { + 'round': {'min': 0, 'max': 10}, + } + EXIST_ATTR = 'auth' + SEARCH_ADDITIONAL = { + 'existing_conns': 'swanctl.Connections.Connection', + 'existing_local_auth': 'swanctl.locals.local', + 'existing_remote_auth': 'swanctl.remotes.remote', + } + + def __init__(self, m: AnsibleModule, r: dict, s: Session = None, f: dict = None): + BaseModule.__init__(self=self, m=m, r=r, s=s, f=f) + self.auth = {} + self.existing_conns = None + self.pubkey_link_found = False + self.existing_local_auth = None + self.existing_remote_auth = None + self.existing_pubkeys = None + self.existing_certs = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['connection']): + self.m.fail_json( + "You need to provide a 'connection' to create an IPSec auth!" + ) + + if self.p['authentication'] == 'pubkey' and ( + is_unset(self.p['certificates']) and + is_unset(self.p['public_keys']) + ): + self.m.fail_json( + "You need to provide at least one certificate or public-key to use the 'pubkey' " + 'authentication method!' + ) + + if self.p['authentication'] in ['eap_tls', 'eap_mschapv2', 'eap_radius'] and \ + is_unset(self.p['eap_id']): + self.m.fail_json( + f"You need to provide an 'eap_id' to use the '{self.p['authentication']}' " + 'authentication method!' + ) + + self._base_check() + + if self.p['state'] == 'present': + self.b.find_single_link( + field='connection', + existing=self.existing_conns, + existing_field_id='description', + ) + + if self.p['authentication'] == 'pubkey': + try: + self._find_links_pubkeys_certs() + self.pubkey_link_found = True + + except ModuleSoftError: + # if neither a local nor a remote authentication-entry exists -> + # we must create one first to get the relevant entries + pass + + def create(self) -> None: + if self.p['authentication'] == 'pubkey' and not self.pubkey_link_found: + # creating dummy-auth-entry to use for getting cert/pubkey infos + self.p['authentication'] = 'psk' + certs, pubkeys = self.p['certificates'], self.p['public_keys'] + self.p['certificates'], self.p['public_keys'] = [], [] + self.b.create() + + self.auth = {} + self.existing_entries = None + self.existing_local_auth = None + self.existing_remote_auth = None + + self.p['certificates'], self.p['public_keys'] = certs, pubkeys + self.p['authentication'] = 'pubkey' + self._base_check() + self._find_links_pubkeys_certs() + + self.b.update() + + else: + self.b.create() + + def _get_existing_pubkeys_certs(self, search: str, existing_target: str): + for existing in [self.existing_local_auth, self.existing_remote_auth]: + if not is_unset(existing): + first = list(existing.keys())[0] + if search in existing[first]: + setattr(self, existing_target, existing[first][search]) + + def _find_links_pubkeys_certs(self): + if not is_unset(self.p['certificates']): + self._get_existing_pubkeys_certs( + search='certs', + existing_target='existing_certs', + ) + self.b.find_multiple_links( + existing_field_id='value', + field='certificates', + existing=self.existing_certs, + fail_soft=True, fail=False, + ) + + else: + self._get_existing_pubkeys_certs( + search='pubkeys', + existing_target='existing_pubkeys', + ) + self.b.find_multiple_links( + existing_field_id='value', + field='public_keys', + existing=self.existing_pubkeys, + fail_soft=True, fail=False + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth_local.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth_local.py new file mode 100644 index 0000000..942d559 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth_local.py @@ -0,0 +1,21 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_auth import \ + BaseAuth + + +class Auth(BaseAuth): + CMDS = { + 'add': 'addLocal', + 'del': 'delLocal', + 'set': 'setLocal', + 'search': 'get', + 'toggle': 'toggleLocal', + } + API_KEY_PATH = 'swanctl.locals.local' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseAuth.__init__(self=self, m=module, r=result, s=session, f=fail) + self.auth = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth_remote.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth_remote.py new file mode 100644 index 0000000..86b8875 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_auth_remote.py @@ -0,0 +1,21 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_auth import \ + BaseAuth + + +class Auth(BaseAuth): + CMDS = { + 'add': 'addRemote', + 'del': 'delRemote', + 'set': 'setRemote', + 'search': 'get', + 'toggle': 'toggleRemote', + } + API_KEY_PATH = 'swanctl.remotes.remote' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseAuth.__init__(self=self, m=module, r=result, s=session, f=fail) + self.auth = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_cert.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_cert.py new file mode 100644 index 0000000..dcfc4cc --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_cert.py @@ -0,0 +1,90 @@ +import re +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_selected, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class KeyPair(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'search_item', + 'detail': 'get_item', + } + API_KEY_PATH = 'keyPair' + API_MOD = 'ipsec' + API_CONT = 'key_pairs' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['public_key'] + FIELDS_ALL = ['name', 'private_key', 'type'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'type': 'keyType', + 'public_key': 'publicKey', + 'private_key': 'privateKey', + } + FIELDS_DIFF_NO_LOG = ['private_key'] + EXIST_ATTR = 'key' + FIELDS_TYPING = {} + TIMEOUT = 30.0 # ipsec reload + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.key = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['public_key']) or is_unset(self.p['private_key']): + self.m.fail_json( + "You need to supply both 'public_key' and " + "'private_key' to create an IPSec certificate!" + ) + + pub_start, pub_end = '-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----' + if self.p['public_key'].find(pub_start) == -1 or \ + self.p['public_key'].find(pub_end) == -1: + self.m.fail_json("The provided 'public_key' has an invalid format!") + + priv_start, priv_end = '-----BEGIN (RSA | EC )?PRIVATE KEY-----', '-----END (RSA | EC )?PRIVATE KEY----- *$' + if ( + re.match(priv_start, self.p['private_key']) is None or + re.search(priv_end, self.p['private_key']) is None + ): + self.m.fail_json( + "The provided 'private_key' has an invalid format - should be " + f"'{self.p['type'].upper()} PRIVATE KEY'!" + ) + + self.p['public_key'] = self.p['public_key'].strip() + self.p['private_key'] = self.p['private_key'].strip() + + self._base_check() + + def _simplify_existing(self, key: dict) -> dict: + # makes processing easier + simple = { + 'type': get_selected(key['keyType']), + 'public_key': key.get('publicKey', '').strip(), + 'private_key': key.get('privateKey', '').strip(), + 'name': key['name'], + } + + if 'uuid' in key: + simple['uuid'] = key['uuid'] + + elif self.key is not None and 'uuid' in self.key: + simple['uuid'] = self.key['uuid'] + + else: + simple['uuid'] = None + + return simple + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_child.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_child.py new file mode 100644 index 0000000..cc06240 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_child.py @@ -0,0 +1,71 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Child(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addChild', + 'del': 'delChild', + 'set': 'setChild', + 'search': 'get', + 'toggle': 'toggleChild', + } + API_KEY_PATH = 'swanctl.children.child' + API_MOD = 'ipsec' + API_CONT = 'connections' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'connection', 'request_id', 'esp_proposals', 'sha256_96', 'start_action', + 'close_action', 'dpd_action', 'mode', 'policies', 'local_net', 'remote_net', + 'rekey_seconds', + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + 'request_id': 'reqid', + 'rekey_seconds': 'rekey_time', + 'local_net': 'local_ts', + 'remote_net': 'remote_ts', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'policies', 'sha256_96'], + 'list': ['esp_proposals', 'local_net', 'remote_net'], + 'select': ['mode', 'dpd_action', 'close_action', 'start_action', 'connection'], + 'int': ['rekey_seconds', 'request_id'], + } + INT_VALIDATIONS = { + 'request_id': {'min': 1, 'max': 65535}, + 'rekey_seconds': {'min': 0, 'max': 500000}, + } + EXIST_ATTR = 'child' + SEARCH_ADDITIONAL = { + 'existing_conns': 'swanctl.Connections.Connection', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.child = {} + self.existing_conns = None + + def check(self) -> None: + if self.p['state'] == 'present': + for field in ['connection', 'local_net', 'remote_net']: + if is_unset(self.p[field]): + self.m.fail_json( + f"You need to provide a '{field}' to create an IPSec child!" + ) + + self._base_check() + if self.p['state'] == 'present': + self.b.find_single_link( + field='connection', + existing=self.existing_conns, + existing_field_id='description', + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_connection.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_connection.py new file mode 100644 index 0000000..a88e708 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_connection.py @@ -0,0 +1,97 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Connection(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addConnection', + 'del': 'delConnection', + 'set': 'setConnection', + 'search': 'get', + 'toggle': 'toggleConnection', + } + API_KEY_PATH = 'swanctl.Connections.Connection' + API_KEY_PATH_REQ = 'connection' + API_MOD = 'ipsec' + API_CONT = 'connections' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'local_addresses', 'remote_addresses', 'pools', 'proposals', 'unique', + 'aggressive', 'version', 'mobike', 'encapsulation', 'reauth_seconds', + 'rekey_seconds', 'over_seconds', 'dpd_delay_seconds', 'dpd_timeout_seconds', + 'send_certificate_request', 'send_certificate', 'keying_tries', 'local_port', 'remote_port' + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + 'encapsulation': 'encap', + 'local_addresses': 'local_addrs', + 'remote_addresses': 'remote_addrs', + 'reauth_seconds': 'reauth_time', + 'rekey_seconds': 'rekey_time', + 'over_seconds': 'over_time', + 'dpd_delay_seconds': 'dpd_delay', + 'dpd_timeout_seconds': 'dpd_timeout', + 'send_certificate_request': 'send_certreq', + 'send_certificate': 'send_cert', + 'keying_tries': 'keyingtries', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'aggressive', 'mobike', 'encapsulation', 'send_certificate_request'], + 'list': ['local_addresses', 'remote_addresses', 'pools', 'proposals'], + 'select': ['send_certificate', 'unique', 'local_port', 'remote_port'], + 'select_opt_list': ['version'], # don't know why this is a list instead of a dict + 'int': [ + 'keying_tries', 'dpd_timeout_seconds', 'dpd_delay_seconds', 'over_seconds', + 'rekey_seconds', 'reauth_seconds', + ], + } + FIELDS_VALUE_MAPPING = { # sending + 'version': { + 'ikev1+2': 0, + 'ikev1': 1, + 'ikev2': 2, + }, + 'local_port': {'500': ''}, + 'remote_port': {'500': ''}, + } + FIELDS_VALUE_MAPPING_RCV = { # receiving + 'version': { + 'ikev1+2': 'IKEv1+IKEv2', + 'ikev1': 'IKEv1', + 'ikev2': 'IKEv2', + }, + 'local_port': {'500': ''}, + 'remote_port': {'500': ''}, + } + INT_VALIDATIONS = { + 'keying_tries': {'min': 0, 'max': 1000}, + 'dpd_timeout_seconds': {'min': 0, 'max': 500000}, + 'dpd_delay_seconds': {'min': 0, 'max': 500000}, + 'over_seconds': {'min': 0, 'max': 500000}, + 'rekey_seconds': {'min': 0, 'max': 500000}, + 'reauth_seconds': {'min': 0, 'max': 500000}, + } + EXIST_ATTR = 'tunnel' + SEARCH_ADDITIONAL = { + 'existing_pools': 'swanctl.Pools.Pool', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.tunnel = {} + self.existing_pools = None + + def check(self) -> None: + self._base_check() + + if self.p['state'] == 'present': + self.b.find_multiple_links( + field='pools', + existing=self.existing_pools, + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_general.py new file mode 100644 index 0000000..4ebb066 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_general.py @@ -0,0 +1,128 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get' + } + API_KEY_PATH = 'ipsec' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'ipsec' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'prefer_old_sa', 'disable_vpn_rules', 'passthrough_networks', 'authentication', 'local_group', + 'radius_servers', 'radius_accounting', 'radius_class_group', 'pam_service', 'pam_session', + 'pam_trim_email', 'charon_max_ikev1_exchanges', 'charon_threads', 'charon_ikesa_table_size', + 'charon_ikesa_table_segments', 'charon_init_limit_half_open', 'charon_ignore_acquire_ts', + 'charon_make_before_break', 'charon_install_routes', 'charon_cisco_unity', 'retransmit_tries', + 'retransmit_timeout', 'retransmit_base', 'retransmit_jitter', 'retransmit_limit', + 'syslog_log_name', 'syslog_log_level', 'syslog_app', 'syslog_asn', + 'syslog_cfg', 'syslog_chd', 'syslog_dmn', 'syslog_enc', 'syslog_esp', 'syslog_ike', 'syslog_imc', + 'syslog_imv', 'syslog_job', 'syslog_knl', 'syslog_lib', 'syslog_mgr', 'syslog_net', 'syslog_pts', + 'syslog_tls', 'syslog_tnc', 'attr_subnet', 'attr_dns', 'attr_nbns', 'unity_split_include', + 'unity_dns_search', 'unity_dns_split', 'unity_login_banner', 'unity_save_password', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'prefer_old_sa': ('general', 'preferred_oldsa'), + 'disable_vpn_rules': ('general', 'disablevpnrules'), + 'passthrough_networks': ('general', 'passthrough_networks'), + 'authentication': ('general', 'user_source'), + 'local_group': ('general', 'local_group'), + 'radius_servers': ('charon', 'plugins', 'eap-radius', 'servers'), + 'radius_accounting': ('charon', 'plugins', 'eap-radius', 'accounting'), + 'radius_class_group': ('charon', 'plugins', 'eap-radius', 'class_group'), + 'pam_service': ('charon', 'plugins', 'xauth-pam', 'pam_service'), + 'pam_session': ('charon', 'plugins', 'xauth-pam', 'session'), + 'pam_trim_email': ('charon', 'plugins', 'xauth-pam', 'trim_email'), + 'charon_max_ikev1_exchanges': ('charon', 'max_ikev1_exchanges'), + 'charon_threads': ('charon', 'threads'), + 'charon_ikesa_table_size': ('charon', 'ikesa_table_size'), + 'charon_ikesa_table_segments': ('charon', 'ikesa_table_segments'), + 'charon_init_limit_half_open': ('charon', 'init_limit_half_open'), + 'charon_ignore_acquire_ts': ('charon', 'ignore_acquire_ts'), + 'charon_make_before_break': ('charon', 'make_before_break'), + 'charon_install_routes': ('charon', 'install_routes'), + 'charon_cisco_unity': ('charon', 'cisco_unity'), + 'retransmit_tries': ('charon', 'retransmit_tries'), + 'retransmit_timeout': ('charon', 'retransmit_timeout'), + 'retransmit_base': ('charon', 'retransmit_base'), + 'retransmit_jitter': ('charon', 'retransmit_jitter'), + 'retransmit_limit': ('charon', 'retransmit_limit'), + 'syslog_log_name': ('charon', 'syslog', 'daemon', 'ike_name'), + 'syslog_log_level': ('charon', 'syslog', 'daemon', 'log_level'), + 'syslog_app': ('charon', 'syslog', 'daemon', 'app'), + 'syslog_asn': ('charon', 'syslog', 'daemon', 'asn'), + 'syslog_cfg': ('charon', 'syslog', 'daemon', 'cfg'), + 'syslog_chd': ('charon', 'syslog', 'daemon', 'app'), + 'syslog_dmn': ('charon', 'syslog', 'daemon', 'dmn'), + 'syslog_enc': ('charon', 'syslog', 'daemon', 'enc'), + 'syslog_esp': ('charon', 'syslog', 'daemon', 'esp'), + 'syslog_ike': ('charon', 'syslog', 'daemon', 'ike'), + 'syslog_imc': ('charon', 'syslog', 'daemon', 'imc'), + 'syslog_imv': ('charon', 'syslog', 'daemon', 'imv'), + 'syslog_job': ('charon', 'syslog', 'daemon', 'job'), + 'syslog_knl': ('charon', 'syslog', 'daemon', 'app'), + 'syslog_lib': ('charon', 'syslog', 'daemon', 'knl'), + 'syslog_mgr': ('charon', 'syslog', 'daemon', 'mgr'), + 'syslog_net': ('charon', 'syslog', 'daemon', 'net'), + 'syslog_pts': ('charon', 'syslog', 'daemon', 'pts'), + 'syslog_tls': ('charon', 'syslog', 'daemon', 'tls'), + 'syslog_tnc': ('charon', 'syslog', 'daemon', 'tnc'), + 'attr_subnet': ('charon', 'plugins', 'attr', 'subnet'), + 'attr_dns': ('charon', 'plugins', 'attr', 'dns'), + 'attr_nbns': ('charon', 'plugins', 'attr', 'nbns'), + 'unity_split_include': ('charon', 'plugins', 'attr', 'split-include'), + 'unity_dns_search': ('charon', 'plugins', 'attr', 'x_28674'), + 'unity_dns_split': ('charon', 'plugins', 'attr', 'x_28675'), + 'unity_login_banner': ('charon', 'plugins', 'attr', 'x_28672'), + 'unity_save_password': ('charon', 'plugins', 'attr', 'x_28673'), + } + FIELDS_TYPING = { + 'bool': [ + 'prefer_old_sa', 'disable_vpn_rules', 'radius_accounting', 'radius_class_group', 'pam_session', + 'pam_trim_email', 'charon_ignore_acquire_ts', 'charon_make_before_break', 'charon_install_routes', + 'charon_cisco_unity', 'syslog_log_name', 'syslog_log_level', 'unity_save_password', + ], + 'int': [ + 'charon_max_ikev1_exchanges', 'charon_threads', 'charon_ikesa_table_size', 'charon_ikesa_table_segments', + 'charon_init_limit_half_open', 'retransmit_tries', 'retransmit_timeout', 'retransmit_base', + 'retransmit_jitter', 'retransmit_limit', + ], + 'list': [ + 'passthrough_networks', 'authentication', 'radius_servers', 'attr_subnet', 'attr_dns', 'attr_nbns', + 'unity_split_include', + ], + 'select': [ + 'local_group', 'syslog_app', 'syslog_asn', 'syslog_cfg', 'syslog_chd', 'syslog_dmn', 'syslog_enc', + 'syslog_esp', 'syslog_ike', 'syslog_imc', 'syslog_imv', 'syslog_job', 'syslog_knl', 'syslog_lib', + 'syslog_mgr', 'syslog_net', 'syslog_pts', 'syslog_tls', 'syslog_tnc', + ], + } + INT_VALIDATIONS = { + 'charon_threads': {'min': 1, 'max': 65536}, + 'charon_ikesa_table_size': {'min': 1, 'max': 65536}, + 'charon_ikesa_table_segments': {'min': 1, 'max': 65536}, + 'charon_init_limit_half_open': {'min': 1, 'max': 65536}, + } + SEARCH_ADDITIONAL = { + 'existing_groups': 'ipsec.general.local_group', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + self.existing_groups = {} + + def check(self) -> None: + self._base_check() + self.b.find_single_link( + field='local_group', + existing=self.existing_groups, + existing_field_id='value', + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_manual_spd.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_manual_spd.py new file mode 100644 index 0000000..3dc147b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_manual_spd.py @@ -0,0 +1,83 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset, MATCH_UUID + + +class ManualSPD(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'set', + 'search': 'search', + 'detail': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'spd' + API_MOD = 'ipsec' + API_CONT = 'manual_spd' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['request_id', 'connection_child', 'source', 'destination', 'name'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + 'request_id': 'reqid', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': [], + 'select': ['connection_child'], + 'int': ['request_id'], + } + INT_VALIDATIONS = { + 'request_id': {'min': 1, 'max': 65535}, + } + EXIST_ATTR = 'spd' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.spd = {} + + def check(self) -> None: + self._base_check() + + if self.p['state'] == 'present' and not is_unset(self.p['connection_child']): + self.b.find_single_link( + field='connection_child', + existing=self._search_connection_child(), + existing_field_id='value', + ) + + def _search_connection_child(self) -> dict: + res = self.s.get(cnf={ + **self.call_cnf, **{'command': self.CMDS['detail']} + })['spd']['connection_child'] + + # temporary workaround for API-bug: https://github.com/opnsense/core/issues/9224 & 9365 + tmp_fix = False + for values in res.values(): + if 'value' not in values: + continue + + if values['value'].startswith(' -'): + tmp_fix = True + break + + if values['value'].find(' - ') != -1: + connection, child = values['value'].split(' - ', 1) + if MATCH_UUID.match(connection.strip()) is not None: + tmp_fix = True + values['value'] = f" - {child.strip()}" + + if tmp_fix: + if '-' in self.p['connection_child']: + self.p['connection_child'] = self.p['connection_child'].split('-')[1] + + self.p['connection_child'] = f" - {self.p['connection_child'].strip()}" + + return res diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_pool.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_pool.py new file mode 100644 index 0000000..0943993 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_pool.py @@ -0,0 +1,52 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Pool(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'set', + 'search': 'search', + 'detail': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'pool' + API_MOD = 'ipsec' + API_CONT = 'pools' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['network', 'dns'] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = {'network': 'addrs'} + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['dns'] + } + EXIST_ATTR = 'pool' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.pool = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['network']): + self.m.fail_json("You need to provide a 'network' to create an IPSec-Pool!") + + for ip in self.p['dns']: + if is_ip(ip): + continue + self.m.fail_json( + f"It seems you provided an invalid IP address as dns: '{ip}'" + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_psk.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_psk.py new file mode 100644 index 0000000..ad22c19 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_psk.py @@ -0,0 +1,52 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class PreSharedKey(BaseModule): + FIELD_ID = 'identity_local' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'search_item', + 'detail': 'get_item', + } + + API_KEY_PATH = 'preSharedKey' + API_MOD = 'ipsec' + API_CONT = 'pre_shared_keys' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['identity_remote', 'psk', 'type'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'identity_local': 'ident', + 'identity_remote': 'remote_ident', + 'psk': 'Key', + 'type': 'keyType', + } + FIELDS_TYPING = { + 'select': ['type'], + } + EXIST_ATTR = 'psk' + TIMEOUT = 30.0 # ipsec reload + FIELDS_DIFF_NO_LOG = ['psk'] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.psk = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['psk']): + self.m.fail_json('You need to supply a PSK!') + + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_vti.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_vti.py new file mode 100644 index 0000000..ff014fd --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/ipsec_vti.py @@ -0,0 +1,66 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Vti(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'set', + 'search': 'search', + 'detail': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'vti' + API_MOD = 'ipsec' + API_CONT = 'vti' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'request_id', 'local_address', 'remote_address', + 'local_tunnel_address', 'remote_tunnel_address', + 'local_tunnel_secondary_address', 'remote_tunnel_secondary_address', + 'skip_firewall', + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + 'request_id': 'reqid', + 'local_address': 'local', + 'remote_address': 'remote', + 'local_tunnel_address': 'tunnel_local', + 'remote_tunnel_address': 'tunnel_remote', + 'local_tunnel_secondary_address': 'tunnel_local2', + 'remote_tunnel_secondary_address': 'tunnel_remote2', + 'skip_firewall': 'skip_fw' + } + FIELDS_TYPING = { + 'int': ['request_id'], + 'bool': ['skip_firewall'], + } + INT_VALIDATIONS = { + 'request_id': {'min': 1, 'max': 65535}, + } + EXIST_ATTR = 'vti' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.vti = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['local_address']) or is_unset(self.p['remote_address']) or \ + is_unset(self.p['local_tunnel_address']) or \ + is_unset(self.p['remote_tunnel_address']): + self.m.fail_json( + "You need to provide a 'local_address', 'remote_address', " + "'local_tunnel_address' and 'remote_tunnel_address' to create an IPSec VTI!" + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_alert.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_alert.py new file mode 100644 index 0000000..270cf58 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_alert.py @@ -0,0 +1,50 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_valid_email + + +class Alert(BaseModule): + CMDS = { + 'add': 'add_alert', + 'del': 'del_alert', + 'set': 'set_alert', + 'search': 'get', + 'toggle': 'toggle_alert', + } + API_KEY_PATH = 'monit.alert' + API_MOD = 'monit' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['recipient', 'not_on', 'events', 'format', 'reminder', 'description'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'not_on': 'noton', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'not_on'], + 'list': ['events'], + 'int': ['reminder'], + } + INT_VALIDATIONS = { + 'reminder': {'min': 0, 'max': 86400}, + } + EXIST_ATTR = 'alert' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.alert = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if not is_valid_email(self.p['recipient']): + self.m.fail_json( + f"The recipient value '{self.p['recipient']}' is not a " + f"valid email address!" + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_service.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_service.py new file mode 100644 index 0000000..0f6cc86 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_service.py @@ -0,0 +1,85 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Service(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_service', + 'del': 'del_service', + 'set': 'set_service', + 'search': 'get', + 'toggle': 'toggle_service', + } + API_KEY_PATH = 'monit.service' + API_MOD = 'monit' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'type', 'pidfile', 'match', 'path', 'service_timeout', 'address', 'interface', + 'start', 'stop', 'tests', 'depends', 'polltime', 'description', + ] + FIELDS_ALL = ['name', 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + INT_VALIDATIONS = { + 'service_timeout': {'min': 1, 'max': 86400}, + } + FIELDS_TRANSLATE = { + 'service_timeout': 'timeout', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['tests', 'depends'], + 'select': ['type', 'interface'], + 'int': ['service_timeout'], + } + EXIST_ATTR = 'service' + SEARCH_ADDITIONAL = { + 'existing_tests': 'monit.test', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.service = {} + self.existing_tests = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['type']): + self.m.fail_json("You need to provide a 'type' to create a service!") + + elif self.p['type'] == 'network' and is_unset(self.p['interface']) and is_unset(self.p['address']): + self.m.fail_json( + "You need to provide either an 'interface' or 'address' " + "to create a network service!" + ) + + elif self.p['type'] == 'host' and is_unset(self.p['address']): + self.m.fail_json( + "You need to provide an 'address' to create " + "a remote-host service!" + ) + + if not is_unset(self.p['address']) and not is_ip(self.p['address']): + self.m.fail_json( + f"The address value '{self.p['address']}' is not a valid IP!" + ) + + self.b.find(match_fields=[self.FIELD_ID]) + + if self.p['state'] == 'present': + self.b.find_multiple_links( + field='tests', + existing=self.existing_tests, + ) + self.b.find_multiple_links( + field='depends', + existing=self.existing_entries, + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_test.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_test.py new file mode 100644 index 0000000..dab7a2b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/monit_test.py @@ -0,0 +1,49 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset + + +class Test(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_test', + 'del': 'del_test', + 'set': 'set_test', + 'search': 'get', + } + API_KEY_PATH = 'monit.test' + API_MOD = 'monit' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['type', 'condition', 'action', 'path'] + FIELDS_ALL = ['name'] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'test' + FIELDS_TYPING = { + 'select': ['type', 'action'], + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.test = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['condition']): + self.m.fail_json( + "You need to provide a 'condition' to create a test!" + ) + + if self.p['action'] == 'execute' and is_unset(self.p['path']): + self.m.fail_json( + "You need to provide the path to a executable to " + "create a test of type 'execute'!" + ) + + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nat_one_to_one.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nat_one_to_one.py new file mode 100644 index 0000000..f44cfb3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nat_one_to_one.py @@ -0,0 +1,59 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class OneToOne(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_rule', + 'del': 'del_rule', + 'set': 'set_rule', + 'search': 'get', + 'toggle': 'toggle_rule', + } + API_KEY_PATH = 'filter.onetoone.rule' + API_MOD = 'firewall' + API_CONT = 'one_to_one' + FIELDS_CHANGE = [ + 'log', 'sequence', 'interface', 'type', 'source_net', 'source_invert', 'destination_net', 'destination_invert', + 'external', 'nat_reflection', 'description' + + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'source_invert': 'source_not', + 'destination_invert': 'destination_not', + 'nat_reflection': 'natreflection', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'log', 'source_invert', 'destination_invert'], + 'list': [], + 'select': ['interface', 'type', 'nat_reflection'], + 'int': [], + } + INT_VALIDATIONS = { + 'sequence': {'min': 1, 'max': 99999}, + } + EXIST_ATTR = 'rule' + API_CMD_REL = 'apply' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.rule = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['interface']): + self.m.fail_json( + "You need to provide an 'interface' to create a one-to-one" + ) + + self.b.find(match_fields=self.p['match_fields']) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nat_source.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nat_source.py new file mode 100644 index 0000000..2826e9a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nat_source.py @@ -0,0 +1,90 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.rule import \ + validate_values +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class SNat(BaseModule): + CMDS = { + 'add': 'add_rule', + 'del': 'del_rule', + 'set': 'set_rule', + 'search': 'get', + 'toggle': 'toggle_rule', + } + API_KEY_PATH = 'filter.snatrules.rule' + API_MOD = 'firewall' + API_CONT = 'source_nat' + FIELDS_CHANGE = [ + 'sequence', 'no_nat', 'interface', 'target', 'target_port', 'description', + 'ip_protocol', 'protocol', 'source_invert', 'source_net', 'source_port', + 'destination_invert', 'destination_net', 'destination_port', 'log', + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'ip_protocol': 'ipprotocol', + 'source_invert': 'source_not', + 'destination_invert': 'destination_not', + 'no_nat': 'nonat', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'log', 'source_invert', 'no_nat', 'destination_invert'], + 'list': [], + 'select': ['interface', 'ip_protocol', 'protocol'], + 'int': [], + } + INT_VALIDATIONS = { + 'sequence': {'min': 1, 'max': 99999}, + } + EXIST_ATTR = 'rule' + API_CMD_REL = 'apply' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.rule = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['interface']): + self.m.fail_json( + "You need to provide an 'interface' to create a source-nat rule!" + ) + + if is_unset(self.p['target']): + self.m.fail_json( + "You need to provide an 'target' to create a source-nat rule!" + ) + + self._build_log_name() + self.b.find(match_fields=self.p['match_fields']) + + if self.p['state'] == 'present': + validate_values(module=self.m, cnf=self.p, error_func=self.m.fail_json, kind='nat') + + self._base_check() + + def _build_log_name(self) -> str: + if self.p['description'] not in [None, '']: + log_name = self.p['description'] + + else: + log_name = 'FROM ' + + if self.p['source_invert']: + log_name += 'NOT ' + + log_name += f"{self.p['source_net']} <= PROTO {self.p['protocol']} => " + + if self.p['destination_invert']: + log_name += 'NOT ' + + log_name += f"{self.p['destination_net']}:{self.p['destination_port']} " + log_name += f" =NAT=> {self.p['target']}:{self.p['target_port']}" + + return log_name diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/neighbor.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/neighbor.py new file mode 100644 index 0000000..ce01e78 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/neighbor.py @@ -0,0 +1,49 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_valid_mac_address, is_ip +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Neighbor(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + 'toggle': 'toggleItem', + } + API_KEY_PATH = 'neighbor.neighbor' + API_MOD = 'interfaces' + API_CONT = 'neighbor_settings' + FIELDS_CHANGE = ['ethernet_address', 'ip_address'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'description': 'descr', + 'ethernet_address': 'etheraddr', + 'ip_address': 'ipaddress', + } + FIELDS_TYPING = { + } + EXIST_ATTR = 'neighbor' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.neighbor = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if not is_valid_mac_address(self.p['ethernet_address']): + self.m.fail_json( + "You need to provide an valid 'ethernet_address' to create a neigbor!" + ) + if not is_ip(self.p['ip_address']): + self.m.fail_json( + "You need to provide an valid 'ip_address' to create a neigbor!" + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nginx_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nginx_general.py new file mode 100644 index 0000000..7958be0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nginx_general.py @@ -0,0 +1,32 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + FIELD_ID = 'name' + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'nginx.general' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'nginx' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'enabled', 'ban_ttl', + ] + FIELDS_ALL = FIELDS_CHANGE + INT_VALIDATIONS = { + 'ban_ttl': {'min': 0}, + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'int': ['ban_ttl'], + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nginx_upstream_server.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nginx_upstream_server.py new file mode 100644 index 0000000..6665edd --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/nginx_upstream_server.py @@ -0,0 +1,50 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset + + +class UpstreamServer(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'addupstreamserver', + 'del': 'delupstreamserver', + 'detail': 'getupstreamserver', + 'search': 'searchupstreamserver', + 'set': 'setupstreamserver', + } + API_KEY_PATH = 'upstream_server' + API_MOD = 'nginx' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'description', 'server', 'port', 'priority', + 'max_conns', 'max_fails', 'fail_timeout', 'no_use' + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TYPING = { + 'select': ['no_use'], + } + INT_VALIDATIONS = { + 'priority': {'min': 0, 'max': 1000000000}, + 'port': {'min': 1, 'max': 65535}, + } + EXIST_ATTR = 'upstream_server' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.upstream_server = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['port']) or is_unset(self.p['priority']) or is_unset(self.p['server']): + self.m.fail_json( + "You need to provide a 'port', 'server' and 'priority' to create a server!" + ) + + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_client.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_client.py new file mode 100644 index 0000000..1bf61be --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_client.py @@ -0,0 +1,114 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_key_by_value_from_selection, get_key_by_value_end_from_selection +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Client(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'set', + 'search': 'search', + 'detail': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'instance' + API_MOD = 'openvpn' + API_CONT = 'instances' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'remote', 'protocol', 'port', 'address', 'mode', 'log_level', 'keepalive_interval', 'keepalive_timeout', + 'carp_depend_on', 'certificate', 'ca', 'key', 'authentication', 'username', 'password', 'renegotiate_time', + 'network_local', 'network_remote', 'options', 'mtu', 'fragment_size', 'mss_fix', 'verify_remote_certificate', + 'http_proxy', + ] + FIELDS_ALL = ['role', 'enabled', 'vpnid', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + 'protocol': 'proto', + 'address': 'local', + 'mode': 'dev_type', + 'log_level': 'verb', + 'certificate': 'cert', + 'authentication': 'auth', + 'renegotiate_time': 'reneg-sec', + 'network_local': 'push_route', + 'network_remote': 'route', + 'options': 'various_flags', + 'mtu': 'tun_mtu', + 'fragment_size': 'fragment', + 'mss_fix': 'mssfix', + 'key': 'tls_key', + 'verify_remote_certificate': 'remote_cert_tls', + 'http_proxy': 'http-proxy', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'mss_fix', 'verify_remote_certificate'], + 'list': ['network_local', 'network_remote', 'options', 'remote'], + 'select': [ + 'certificate', 'ca', 'key', 'authentication', 'carp_depend_on', 'log_level', + 'mode', 'protocol', 'role', + ], + 'select_opt_list_idx': ['log_level'], + 'int': ['fragment_size', 'mtu'], + } + INT_VALIDATIONS = { + 'mtu': {'min': 60, 'max': 65535}, + 'fragment_size': {'min': 0, 'max': 65528}, + } + EXIST_ATTR = 'instance' + FIELDS_DIFF_EXCLUDE = ['vpnid', 'role'] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.instance = {} + + def check(self) -> None: + self.p['role'] = 'client' + + if self.p['state'] == 'present': + if is_unset(self.p['remote']): + self.m.fail_json( + "You need to provide a 'remote' target to create an openvpn-client!" + ) + + if is_unset(self.p['certificate']) and is_unset(self.p['ca']): + self.m.fail_json( + "You need to either provide a 'certificate' or 'ca' to create an openvpn-client!" + ) + + self._base_check() + + if not is_unset(self.p['ca']): + self.p['ca'] = get_key_by_value_from_selection( + selection=self.b.raw['ca'], + value=self.p['ca'], + ) + + if not is_unset(self.p['certificate']): + self.p['certificate'] = get_key_by_value_from_selection( + selection=self.b.raw[self.FIELDS_TRANSLATE['certificate']], + value=self.p['certificate'], + ) + + if not is_unset(self.p['key']): + self.p['key'] = get_key_by_value_end_from_selection( + selection=self.b.raw[self.FIELDS_TRANSLATE['key']], + value=self.p['key'], + ) + + if self.p['state'] == 'present': + if 'before' in self.r['diff'] and 'mode' in self.r['diff']['before']: + self.r['diff']['before']['mode'] = self.r['diff']['before']['mode'].lower() + self.instance['mode'] = self.r['diff']['before']['mode'] + + self.r['diff']['after'] = self.b.build_diff(data=self.p) + self.r['changed'] = self.r['diff']['before'] != self.r['diff']['after'] diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_client_override.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_client_override.py new file mode 100644 index 0000000..885b18d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_client_override.py @@ -0,0 +1,68 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset, get_key_by_value_beg_from_selection +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Override(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'set', + 'toggle': 'toggle', + 'search': 'search', + 'detail': 'get', + } + API_KEY_PATH = 'cso' + API_MOD = 'openvpn' + API_CONT = 'client_overwrites' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'servers', 'description', 'block', 'push_reset', 'network_tunnel_ip4', 'network_tunnel_ip6', + 'network_local', 'network_remote', 'route_gateway', 'redirect_gateway', 'register_dns', + 'domain', 'domain_list', 'dns_servers', 'ntp_servers', 'wins_servers', + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'common_name', + 'network_tunnel_ip4': 'tunnel_network', + 'network_tunnel_ip6': 'tunnel_networkv6', + 'network_local': 'local_networks', + 'network_remote': 'remote_networks', + 'domain': 'dns_domain', + 'domain_list': 'dns_domain_search', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'block', 'push_reset', 'register_dns'], + 'list': [ + 'servers', 'redirect_gateway', 'network_local', 'network_remote', 'domain_list', + 'dns_servers', 'ntp_servers', 'wins_servers', + ], + } + EXIST_ATTR = 'override' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.override = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['servers']): + self.m.fail_json("You need to provide at least one 'servers' to create an Client-Overwrite!") + + self._base_check() + + if not is_unset(self.p['servers']): + servers = [] + for server in self.p['servers']: + servers.append(get_key_by_value_beg_from_selection( + selection=self.b.raw['servers'], + value=server, + )) + + self.p['servers'] = servers diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_server.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_server.py new file mode 100644 index 0000000..cb370fe --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_server.py @@ -0,0 +1,153 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_key_by_value_from_selection, get_key_by_value_end_from_selection +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Server(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'set', + 'search': 'search', + 'detail': 'get', + 'toggle': 'toggle', + } + API_KEY_PATH = 'instance' + API_MOD = 'openvpn' + API_CONT = 'instances' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'protocol', 'port', 'address', 'mode', 'log_level', 'keepalive_interval', 'keepalive_timeout', + 'certificate', 'ca', 'key', 'authentication', 'renegotiate_time', 'network_local', 'network_remote', + 'options', 'mtu', 'fragment_size', 'mss_fix', 'server_ip4', 'server_ip6', 'max_connections', + 'topology', 'crl', 'verify_client_cert', 'cert_depth', 'data_ciphers', 'data_cipher_fallback', + 'ocsp', 'auth_mode', 'auth_group', 'user_as_cn', 'user_cn_strict', 'auth_token_time', 'push_options', + 'redirect_gateway', 'route_metric', 'register_dns', 'domain', 'domain_list', 'dns_servers', + 'ntp_servers', 'port_share', 'pool', 'verify_remote_certificate', 'auth_token_renewal', 'auth_token_secret', + 'require_client_provisioning', 'persist_address_pool', + ] + FIELDS_ALL = ['role', 'enabled', 'vpnid', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + 'protocol': 'proto', + 'address': 'local', + 'mode': 'dev_type', + 'log_level': 'verb', + 'certificate': 'cert', + 'authentication': 'auth', + 'renegotiate_time': 'reneg-sec', + 'network_local': 'push_route', + 'network_remote': 'route', + 'options': 'various_flags', + 'mtu': 'tun_mtu', + 'fragment_size': 'fragment', + 'mss_fix': 'mssfix', + 'key': 'tls_key', + 'server_ip4': 'server', + 'server_ip6': 'server_ipv6', + 'auth_mode': 'authmode', + 'auth_group': 'local_group', + 'max_connections': 'maxclients', + 'user_as_cn': 'username_as_common_name', + 'user_cn_strict': 'strictusercn', + 'auth_token_time': 'auth-gen-token', + 'auth_token_renewal': 'auth-gen-token-renewal', + 'auth_token_secret': 'auth-gen-token-secret', + 'push_options': 'various_push_flags', + 'domain': 'dns_domain', + 'domain_list': 'dns_domain_search', + 'ocsp': 'use_ocsp', + 'data_ciphers': 'data-ciphers', + 'data_cipher_fallback': 'data-ciphers-fallback', + 'port_share': 'port-share', + 'pool': 'nopool', + 'verify_remote_certificate': 'remote_cert_tls', + 'require_client_provisioning': 'provision_exclusive', + 'persist_address_pool': 'ifconfig-pool-persist', + } + FIELDS_BOOL_INVERT = ['pool'] + FIELDS_TYPING = { + 'bool': [ + 'enabled', 'mss_fix', 'ocsp', 'user_as_cn', 'register_dns', 'pool', 'verify_remote_certificate', + 'require_client_provisioning', 'persist_address_pool', + ], + 'list': [ + 'network_local', 'network_remote', 'options', 'data_ciphers', 'auth_mode', 'push_options', + 'redirect_gateway', 'domain_list', 'dns_servers', 'ntp_servers', + ], + 'select': [ + 'certificate', 'ca', 'key', 'authentication', 'carp_depend_on', 'log_level', + 'mode', 'protocol', 'role', 'topology', 'crl', 'verify_client_cert', 'cert_depth', + 'data_cipher_fallback', 'auth_group', 'domain', + ], + 'select_opt_list_idx': ['log_level', 'user_cn_strict'], + 'int': ['fragment_size', 'mtu', 'route_metric', 'auth_token_time', 'auth_token_renewal'], + } + INT_VALIDATIONS = { + 'mtu': {'min': 60, 'max': 65535}, + 'fragment_size': {'min': 0, 'max': 65528}, + 'route_metric': {'min': 0, 'max': 65535}, + } + EXIST_ATTR = 'instance' + FIELDS_DIFF_EXCLUDE = ['vpnid', 'role'] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.instance = {} + + def check(self) -> None: + self.p['role'] = 'server' + + if self.p['state'] == 'present': + if is_unset(self.p['server_ip4']) and is_unset(self.p['server_ip6']): + self.m.fail_json( + "You need to either provide a 'server_ip4' or 'server_ip6' network to create an openvpn-server!" + ) + + if is_unset(self.p['certificate']) and is_unset(self.p['ca']): + self.m.fail_json( + "You need to either provide a 'certificate' or 'ca' to create an openvpn-server!" + ) + + + self._base_check() + + if not is_unset(self.p['ca']): + self.p['ca'] = get_key_by_value_from_selection( + selection=self.b.raw['ca'], + value=self.p['ca'], + ) + + if not is_unset(self.p['certificate']): + self.p['certificate'] = get_key_by_value_from_selection( + selection=self.b.raw[self.FIELDS_TRANSLATE['certificate']], + value=self.p['certificate'], + ) + + if not is_unset(self.p['crl']): + self.p['crl'] = get_key_by_value_from_selection( + selection=self.b.raw[self.FIELDS_TRANSLATE['crl']], + value=self.p['crl'], + ) + + if not is_unset(self.p['key']): + self.p['key'] = get_key_by_value_end_from_selection( + selection=self.b.raw[self.FIELDS_TRANSLATE['key']], + value=self.p['key'], + ) + + if self.p['state'] == 'present': + if 'before' in self.r['diff'] and 'mode' in self.r['diff']['before']: + self.r['diff']['before']['mode'] = self.r['diff']['before']['mode'].lower() + self.instance['mode'] = self.r['diff']['before']['mode'] + + self.r['diff']['after'] = self.b.build_diff(data=self.p) + self.r['changed'] = self.r['diff']['before'] != self.r['diff']['after'] diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_static_key.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_static_key.py new file mode 100644 index 0000000..8bb06d9 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/openvpn_static_key.py @@ -0,0 +1,54 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Key(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_static_key', + 'del': 'del_static_key', + 'set': 'set_static_key', + 'search': 'search_static_key', + 'detail': 'get_static_key', + 'gen': 'gen_key', + } + API_KEY_PATH = 'statickey' + API_MOD = 'openvpn' + API_CONT = 'instances' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['mode', 'key'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + } + FIELDS_TYPING = { + 'select': ['mode'], + } + EXIST_ATTR = 'key' + FIELDS_DIFF_NO_LOG = ['key'] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.key = {} + + def check(self) -> None: + self._base_check() + + if self.p['state'] == 'present': + if is_unset(self.p['key']): + if self.exists: + self.p['key'] = self.key['key'] + + else: + self.p['key'] = self.s.get({ + **self.call_cnf, + 'command': self.CMDS['gen'], + })['key'] + + self.r['diff']['after'] = self.b.build_diff(data=self.p) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/package.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/package.py new file mode 100644 index 0000000..2532739 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/package.py @@ -0,0 +1,131 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_true +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session + + +class Package: + UPGRADE_MSG = 'Installation out of date' + API_MOD = 'core' + API_CONT = 'firmware' + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, name: str, session: Session = None): + self.m = module + self.p = module.params + self.s = Session( + module=module, + timeout=self.TIMEOUT, + ) if session is None else session + self.n = name + self.r = { + 'changed': False, 'version': None, + 'diff': {} + } + self.package_stati = None + self.call_cnf = { + 'module': self.API_MOD, + 'controller': self.API_CONT, + } + + def check(self) -> None: + if self.package_stati is None: + self.package_stati = self.search_call() + + self.r['diff']['before'] = {'installed': False, 'locked': False} + + for pkg_status in self.package_stati: + if pkg_status['name'] == self.n: + if self.p['debug']: + self.m.warn(f"Package status: '{pkg_status}'") + + self.r['diff']['before'] = { + 'version': pkg_status['version'], + 'installed': is_true(pkg_status['installed']), + 'locked': is_true(pkg_status['locked']), + } + break + + self.r['diff']['after'] = self.r['diff']['before'].copy() + + if self.p['action'] in ['install', 'reinstall']: + self.check_system_up_to_date() + + self.check_lock() + self.call_cnf['params'] = [self.n] + + def check_lock(self) -> None: + if self.p['action'] in ['reinstall', 'remove', 'install'] and \ + self.r['diff']['before']['locked']: + self.m.fail_json( + f"Unable to execute action '{self.p['action']}' - " + f"package is locked!" + ) + + def check_system_up_to_date(self) -> bool: + status = self.s.get(cnf={ + 'command': 'upgradestatus', + **self.call_cnf + }) + + if str(status).find(self.UPGRADE_MSG) != -1: + self.m.fail_json( + f"Unable to execute action '{self.p['action']}' - " + f"system needs to be upgraded beforehand!" + ) + + return False + + def get_existing(self) -> list: + entries = [] + + for entry in self.search_call(): + if is_true(entry['installed']): + entries.append({ + 'name': entry['name'], + 'version': entry['version'], + 'locked': is_true(entry['locked']), + }) + + return entries + + def search_call(self) -> dict: + return self.s.get(cnf={'command': 'info', **self.call_cnf})['package'] + + def change_state(self) -> None: + run = False + + if self.p['action'] == 'lock': + if not self.r['diff']['before']['locked']: + run = True + self.r['diff']['after']['locked'] = True + + elif self.p['action'] == 'unlock': + if self.r['diff']['before']['locked']: + run = True + self.r['diff']['after']['locked'] = False + + elif self.p['action'] == 'remove': + self.r['diff']['after']['installed'] = False + run = True + + else: + run = True + + self.r['changed'] = True + if run: + self.execute() + + def execute(self) -> None: + if not self.m.check_mode: + self.s.post(cnf={ + 'command': self.p['action'], + **self.call_cnf + }) + + def install(self) -> None: + self.r['changed'] = True + self.r['diff']['after']['installed'] = True + self.execute() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/package_main.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/package_main.py new file mode 100644 index 0000000..1f55185 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/package_main.py @@ -0,0 +1,36 @@ +from time import sleep + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.package import Package +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session + + +def process(m: AnsibleModule, p: dict, r: dict) -> None: + s = Session(module=m) + + # pulling stati of all packages + package_stati = Package(module=m, session=s, name='').search_call() + + for pkg_name in p['name']: + pkg = Package(module=m, name=pkg_name, session=s) + pkg.package_stati = package_stati + pkg.check() + + # execute action if needed + if pkg.r['diff']['before']['installed'] and \ + p['action'] in ['reinstall', 'remove', 'lock', 'unlock']: + pkg.change_state() + + elif not pkg.r['diff']['before']['installed'] and \ + p['action'] == 'install': + pkg.install() + + if pkg.r['changed']: + sleep(p['post_sleep']) # time for the box to update package info + r['changed'] = True + + r['diff']['before'][pkg_name] = pkg.r['diff']['before'] + r['diff']['after'][pkg_name] = pkg.r['diff']['after'] + + s.close() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_address.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_address.py new file mode 100644 index 0000000..0656905 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_address.py @@ -0,0 +1,34 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Address(BaseModule): + FIELD_ID = 'address' + CMDS = { + 'add': 'addAddress', + 'del': 'delAddress', + 'set': 'setAddress', + 'search': 'get', + 'toggle': 'toggleAddress', + } + API_KEY_PATH = 'address.addresses.address' + API_MOD = 'postfix' + API_CONT = 'address' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['to'] + FIELDS_ALL = ['enabled', 'address'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = {'address': 'from'} + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['to'], + } + EXIST_ATTR = 'address' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.address = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_domain.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_domain.py new file mode 100644 index 0000000..fd0697d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_domain.py @@ -0,0 +1,32 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Domain(BaseModule): + FIELD_ID = 'domainname' + CMDS = { + 'add': 'addDomain', + 'del': 'delDomain', + 'set': 'setDomain', + 'search': 'get', + 'toggle': 'toggleDomain', + } + API_KEY_PATH = 'domain.domains.domain' + API_MOD = 'postfix' + API_CONT = 'domain' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['destination'] + FIELDS_ALL = ['enabled', 'domainname'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + } + EXIST_ATTR = 'domain' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.domain = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_general.py new file mode 100644 index 0000000..1c951bb --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_general.py @@ -0,0 +1,53 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'general' + API_MOD = 'postfix' + API_CONT = 'general' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'myhostname', 'mydomain', 'myorigin', 'inet_interfaces', 'inet_port', 'ip_version', 'bind_address', + 'bind_address6', 'mynetworks', 'banner', 'message_size_limit', 'masquerade_domains', + 'tls_server_compatibility', 'tls_client_compatibility', 'certificate', 'ca', 'smtpclient_security', + 'relayhost', 'smtpauth_enabled', 'smtpauth_user', 'smtpauth_password', 'enforce_recipient_check', + 'extensive_helo_restrictions', 'extensive_sender_restrictions', 'reject_unknown_client_hostname', + 'reject_non_fqdn_helo_hostname', 'reject_invalid_helo_hostname', 'reject_unknown_helo_hostname', + 'reject_unauth_pipelining', 'reject_unknown_sender_domain', 'reject_unknown_recipient_domain', + 'reject_non_fqdn_sender', 'reject_non_fqdn_recipient', 'permit_sasl_authenticated', 'permit_tls_clientcerts', + 'permit_mynetworks', 'reject_unauth_destination', 'reject_unverified_recipient', 'delay_warning_time', + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': [ + 'enabled', 'tlswrappermode', 'smtpauth_enabled', 'enforce_recipient_check', 'extensive_helo_restrictions', + 'extensive_sender_restrictions', 'reject_unknown_client_hostname', 'reject_non_fqdn_helo_hostname', + 'reject_invalid_helo_hostname', 'reject_unknown_helo_hostname', 'reject_unauth_pipelining', + 'reject_unknown_sender_domain', 'reject_unknown_recipient_domain', 'reject_non_fqdn_sender', + 'reject_non_fqdn_recipient', 'permit_sasl_authenticated', 'permit_tls_clientcerts', 'permit_mynetworks', + 'reject_unauth_destination', 'reject_unverified_recipient', + ], + 'list': ['inet_interfaces', 'mynetworks', 'masquerade_domains'], + 'select': [ + 'ip_version', 'tls_server_compatibility', 'tls_client_compatibility', 'certificate', 'ca', + 'smtpclient_security', + ], + 'int': ['inet_port', 'message_size_limit', 'delay_warning_time'], + } + INT_VALIDATIONS = { + 'inet_port': {'min': 1, 'max': 65535}, + 'delay_warning_time': {'min': 0, 'max': 24}, + } + EXIST_ATTR = 'settings' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_headercheck.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_headercheck.py new file mode 100644 index 0000000..47e8125 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_headercheck.py @@ -0,0 +1,35 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Headercheck(BaseModule): + FIELD_ID = 'expression' + CMDS = { + 'add': 'addHeadercheck', + 'del': 'delHeadercheck', + 'set': 'setHeadercheck', + 'search': 'get', + 'toggle': 'toggleHeadercheck', + } + API_KEY_PATH = 'headerchecks.headerchecks.headercheck' + API_MOD = 'postfix' + API_CONT = 'headerchecks' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = [] + FIELDS_ALL = ['enabled', 'expression', 'filter'] + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['filter'], + } + EXIST_ATTR = 'headercheck' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.headercheck = {} + + def check(self) -> None: + self.b.find(match_fields=['expression', 'filter']) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_recipient.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_recipient.py new file mode 100644 index 0000000..986b581 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_recipient.py @@ -0,0 +1,33 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Recipient(BaseModule): + FIELD_ID = 'address' + CMDS = { + 'add': 'addRecipient', + 'del': 'delRecipient', + 'set': 'setRecipient', + 'search': 'get', + 'toggle': 'toggleRecipient', + } + API_KEY_PATH = 'recipient.recipients.recipient' + API_MOD = 'postfix' + API_CONT = 'recipient' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['action'] + FIELDS_ALL = ['enabled', 'address'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + } + EXIST_ATTR = 'recipient' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.recipient = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_recipientbcc.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_recipientbcc.py new file mode 100644 index 0000000..4d381aa --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_recipientbcc.py @@ -0,0 +1,34 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class RecipientBCC(BaseModule): + FIELD_ID = 'address' + CMDS = { + 'add': 'addRecipientbcc', + 'del': 'delRecipientbcc', + 'set': 'setRecipientbcc', + 'search': 'get', + 'toggle': 'toggleRecipientbcc', + } + API_KEY_PATH = 'recipientbcc.recipientbccs.recipientbcc' + API_MOD = 'postfix' + API_CONT = 'recipientbcc' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['to'] + FIELDS_ALL = ['enabled', 'address'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = {'address': 'from'} + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['to'], + } + EXIST_ATTR = 'recipient' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.recipient = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_sender.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_sender.py new file mode 100644 index 0000000..d0b5bec --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_sender.py @@ -0,0 +1,33 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Sender(BaseModule): + FIELD_ID = 'address' + CMDS = { + 'add': 'addSender', + 'del': 'delSender', + 'set': 'setSender', + 'search': 'get', + 'toggle': 'toggleSender', + } + API_KEY_PATH = 'sender.senders.sender' + API_MOD = 'postfix' + API_CONT = 'sender' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['action'] + FIELDS_ALL = ['enabled', 'address'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + } + EXIST_ATTR = 'sender' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.sender = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_senderbcc.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_senderbcc.py new file mode 100644 index 0000000..405004e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_senderbcc.py @@ -0,0 +1,34 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class SenderBCC(BaseModule): + FIELD_ID = 'address' + CMDS = { + 'add': 'addSenderbcc', + 'del': 'delSenderbcc', + 'set': 'setSenderbcc', + 'search': 'get', + 'toggle': 'toggleSenderbcc', + } + API_KEY_PATH = 'senderbcc.senderbccs.senderbcc' + API_MOD = 'postfix' + API_CONT = 'senderbcc' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['to'] + FIELDS_ALL = ['enabled', 'address'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = {'address': 'from'} + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['to'], + } + EXIST_ATTR = 'sender' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.sender = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_sendercanonical.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_sendercanonical.py new file mode 100644 index 0000000..b13bb44 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/postfix_sendercanonical.py @@ -0,0 +1,34 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class SenderCanonical(BaseModule): + FIELD_ID = 'address' + CMDS = { + 'add': 'addSendercanonical', + 'del': 'delSendercanonical', + 'set': 'setSendercanonical', + 'search': 'get', + 'toggle': 'toggleSendercanonical', + } + API_KEY_PATH = 'sendercanonical.sendercanonicals.sendercanonical' + API_MOD = 'postfix' + API_CONT = 'sendercanonical' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['to'] + FIELDS_ALL = ['enabled', 'address'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = {'address': 'from'} + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['to'], + } + EXIST_ATTR = 'sender' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.sender = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/privilege.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/privilege.py new file mode 100644 index 0000000..f5f55da --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/privilege.py @@ -0,0 +1,58 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + get_key_by_value_from_selection +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Privilege(BaseModule): + FIELD_PK = 'id' + FIELD_ID = 'id' + CMDS = { + 'set': 'set_item', + 'search': 'search', + 'detail': 'get_item', + } + API_KEY_PATH = 'priv' + API_MOD = 'auth' + API_CONT = 'priv' + FIELDS_CHANGE = ['user', 'group'] + FIELDS_TYPING = { + 'list': ['user', 'group'], + } + FIELDS_TRANSLATE = { + 'user': 'users', + 'group': 'groups', + } + FIELDS_ALL = ['id'] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'privilege' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.privilege = {} + + def process(self) -> None: + if not self.exists: + self.m.fail_json(f"Privilege {self.p['id']} not found") + + self.p['user'] = [ + get_key_by_value_from_selection(self.b.raw['users'], u) + for u in self.p['user'] + ] + self.p['group'] = [ + get_key_by_value_from_selection(self.b.raw['groups'], g) + for g in self.p['group'] + ] + + if self.p['state'] == 'present': + self.p['user'] = list(set(self.p['user'] + self.privilege['user'])) + self.p['group'] = list(set(self.p['group'] + self.privilege['group'])) + + elif self.p['state'] == 'absent': + self.p['user'] = list(set(self.privilege['user']).difference(self.p['user'])) + self.p['group'] = list(set(self.privilege['group']).difference(self.p['group'])) + + self.update() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/route.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/route.py new file mode 100644 index 0000000..cdd52e0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/route.py @@ -0,0 +1,61 @@ +from ipaddress import ip_network + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + simplify_translate +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Route(BaseModule): + FIELD_ID = 'uuid' + CMDS = { + 'add': 'addroute', + 'del': 'delroute', + 'set': 'setroute', + 'search': 'get', + 'toggle': 'toggleroute', + } + API_KEY_PATH = 'route.route' + API_MOD = 'routes' + API_CONT = 'routes' + FIELDS_CHANGE = ['network', 'gateway', 'description'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_BOOL_INVERT = ['enabled'] + FIELDS_TRANSLATE = { + 'description': 'descr', + 'enabled': 'disabled', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['gateway'], + } + EXIST_ATTR = 'route' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.route = {} + + def check(self) -> None: + try: + ip_network(self.p['network']) + + except ValueError: + self.m.fail_json(f"Value '{self.p['network']}' is not a valid network!") + + self._base_check() + + def _simplify_existing(self, route: dict) -> dict: + simple = simplify_translate( + existing=route, + typing=self.FIELDS_TYPING, + translate=self.FIELDS_TRANSLATE, + bool_invert=self.FIELDS_BOOL_INVERT, + ) + if simple['gateway'].find(' - ') != -1: + simple['gateway'] = simple['gateway'].rsplit('-', 1)[0].strip() + + return simple diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/rule.py new file mode 100644 index 0000000..672eac1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/rule.py @@ -0,0 +1,132 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + ModuleSoftError +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + validate_int_fields +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.rule import \ + validate_values +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Rule(BaseModule): + MULTI_DIFF_KEY = 'description' + CMDS = { + 'add': 'add_rule', + 'del': 'del_rule', + 'set': 'set_rule', + 'search': 'get', + 'toggle': 'toggle_rule', + } + API_KEY_PATH = 'filter.rules.rule' + API_MOD = 'firewall' + API_CONT = 'filter' + FIELDS_CHANGE = [ + 'sequence', 'action', 'quick', 'interface', 'interface_invert', 'direction', + 'ip_protocol', 'protocol', 'source_invert', 'source_net', 'source_port', + 'destination_invert', 'destination_net', 'destination_port', 'log', + 'tag', 'tagged', 'description', 'gateway', 'replyto', 'disable_replyto', + 'allow_opts', 'state_type', 'state_policy', 'state_timeout', + 'max_states', 'max_src_nodes', 'max_src_states', 'max_src_conn', 'max_src_conn_rate', + 'max_src_conn_rates', 'overload', 'adaptive_start', 'adaptive_end', 'prio', 'set_prio', 'set_prio_low', + 'tcp_flags', 'tcp_flags_clear', 'schedule', 'tos', 'icmp_type', + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'interface_invert': 'interfacenot', + 'ip_protocol': 'ipprotocol', + 'source_invert': 'source_not', + 'destination_invert': 'destination_not', + 'disable_replyto': 'disablereplyto', + 'allow_opts': 'allowopts', + 'state_type': 'statetype', + 'state_policy': 'state-policy', + 'state_timeout': 'statetimeout', + 'max_states': 'max', + 'max_src_nodes': 'max-src-nodes', + 'max_src_states': 'max-src-states', + 'max_src_conn': 'max-src-conn', + 'max_src_conn_rate': 'max-src-conn-rate', + 'max_src_conn_rates': 'max-src-conn-rates', + 'adaptive_start': 'adaptivestart', + 'adaptive_end': 'adaptiveend', + 'set_prio': 'set-prio', + 'set_prio_low': 'set-prio-low', + 'tcp_flags': 'tcpflags1', + 'tcp_flags_clear': 'tcpflags2', + 'schedule': 'sched', + 'icmp_type': 'icmptype', + } + FIELDS_TYPING = { + 'bool': [ + 'enabled', 'log', 'quick', 'interface_invert', 'source_invert', 'destination_invert', 'disable_replyto', + 'allow_opts', + ], + 'select': [ + 'action', 'direction', 'ip_protocol', 'protocol', 'gateway', 'replyto', 'state_type', 'state_policy', + 'overload', 'prio', 'set_prio', 'set_prio_low', 'schedule', 'tos', + ], + 'list': ['interface', 'tcp_flags', 'tcp_flags_clear', 'icmp_type'], + 'int': ['sequence', 'state_timeout'], + } + EXIST_ATTR = 'rule' + TIMEOUT = 60.0 # urltable etc reload + INT_VALIDATIONS = { + 'sequence': {'min': 1, 'max': 99999}, + 'state_timeout': {'min': 1}, + } + API_CMD_REL = 'apply' + + def __init__( + self, module: AnsibleModule, result: dict, multi: dict = None, + session: Session = None, fail: dict = None, + ): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail, multi=multi) + self.rule = {} + self.log_name = None + + def _build_log_name(self) -> str: + if self.p['description'] not in [None, '']: + log_name = self.p['description'] + + else: + log_name = f"{self.p['action'].upper()}: FROM " + + if self.p['source_invert']: + log_name += 'NOT ' + + log_name += f"{self.p['source_net']} <= PROTO {self.p['protocol']} => " + + if self.p['destination_invert']: + log_name += 'NOT ' + + log_name += f"{self.p['destination_net']}:{self.p['destination_port']}" + + return log_name + + def check(self) -> None: + if self.p['state'] == 'present': + validate_int_fields( + module=self.m, + data=self.p, + field_minmax=self.INT_VALIDATIONS, + error_func=self._error + ) + + self._build_log_name() + self.b.find(match_fields=self.p['match_fields']) + + if self.p['state'] == 'present': + validate_values(module=self.m, cnf=self.p, error_func=self._error) + + self._base_check() + + def _error(self, msg: str, verification: bool = True) -> None: + if (verification and self.fail_verify) or (not verification and self.fail_process): + self.m.fail_json(msg) + + else: + self.m.warn(msg) + raise ModuleSoftError diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/rule_interface_group.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/rule_interface_group.py new file mode 100644 index 0000000..0f0fee3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/rule_interface_group.py @@ -0,0 +1,54 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Group(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_item', + 'del': 'del_item', + 'set': 'set_item', + 'search': 'get', + } + API_KEY_PATH = 'group.ifgroupentry' + API_KEY_PATH_REQ = 'group' + API_MOD = 'firewall' + API_CONT = 'group' + FIELDS_CHANGE = ['name', 'members', 'gui_group', 'sequence', 'description'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_BOOL_INVERT = ['gui_group'] + FIELDS_TRANSLATE = { + 'name': 'ifname', + 'description': 'descr', + 'gui_group': 'nogroup' + } + FIELDS_TYPING = { + 'bool': ['gui_group'], + 'list': ['members'], + 'select': ['members'], + 'int': ['sequence'], + } + INT_VALIDATIONS = { + 'sequence': {'min': 0, 'max': 9999}, + } + EXIST_ATTR = 'group' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.group = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['members']): + self.m.fail_json("You need to provide a 'members' to create a rule interface group!") + + self._base_check() + + def update(self) -> None: + self.b.update(enable_switch=False) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/savepoint.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/savepoint.py new file mode 100644 index 0000000..9ef927d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/savepoint.py @@ -0,0 +1,73 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import single_post + + +class SavePoint: + def __init__(self, module: AnsibleModule, result: dict, controller: str = None): + self.m = module + self.r = result + self.c = controller if controller is not None else self.m.params['controller'] + self.revision = self.m.params['revision'] + self.call_cnf = { + 'module': self.m.params['api_module'], + 'controller': self.c, + } + + def create(self) -> str: + if not self.m.check_mode: + if self.revision is None: + response = single_post( + module=self.m, + cnf={ + 'command': 'savepoint', + **self.call_cnf, + } + ) + + if 'revision' not in response: + self.m.fail_json(msg='Failed to create savepoint!') + + return response['revision'] + + self.m.fail_json(f"Unable to create savepoint - a revision ('{self.revision}') exists!") + + def _check_revision(self, action: str) -> None: + if self.revision is None: + self.m.fail_json(f"Unable to run action '{action}' - a target revision needs to be provided!") + + def apply(self) -> None: + if not self.m.check_mode: + self._check_revision(action='apply') + single_post( + module=self.m, + cnf={ + 'command': 'apply', + 'params': [self.revision], + **self.call_cnf, + } + ) + + def cancel_rollback(self) -> None: + if not self.m.check_mode: + self._check_revision(action='cancel_rollback') + single_post( + module=self.m, + cnf={ + 'command': 'cancelRollback', + 'params': [self.revision], + **self.call_cnf, + } + ) + + def revert(self) -> None: + if not self.m.check_mode: + self._check_revision(action='revert') + single_post( + module=self.m, + cnf={ + 'command': 'revert', + 'params': [self.revision], + **self.call_cnf, + } + ) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_pipe.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_pipe.py new file mode 100644 index 0000000..ba458fe --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_pipe.py @@ -0,0 +1,67 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Pipe(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_pipe', + 'del': 'del_pipe', + 'set': 'set_pipe', + 'search': 'get', + 'toggle': 'toggle_pipe', + } + API_KEY_PATH = 'ts.pipes.pipe' + API_MOD = 'trafficshaper' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'bandwidth', 'bandwidth_metric', 'queue', 'mask', 'buckets', 'scheduler', + 'codel_enable', 'codel_target', 'codel_interval', 'codel_ecn_enable', + 'pie_enable', 'fqcodel_quantum', 'fqcodel_limit', 'fqcodel_flows', + 'delay', + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'bandwidth_metric': 'bandwidthMetric', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'pie_enable', 'codel_enable', 'codel_ecn_enable'], + 'select': ['bandwidth_metric', 'mask', 'scheduler'], + } + INT_VALIDATIONS = { + # 'id': {'min': 1, 'max': 65535}, + 'queue': {'min': 2, 'max': 100}, + 'buckets': {'min': 1, 'max': 65535}, + 'codel_target': {'min': 1, 'max': 10000}, + 'codel_interval': {'min': 1, 'max': 10000}, + 'fqcodel_quantum': {'min': 1, 'max': 65535}, + 'fqcodel_limit': {'min': 1, 'max': 65535}, + 'fqcodel_flows': {'min': 1, 'max': 65535}, + 'delay': {'min': 1, 'max': 3000}, + } + EXIST_ATTR = 'pipe' + TIMEOUT = 20.0 # 'get' timeout + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.pipe = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['bandwidth']): + self.m.fail_json('You need to provide bandwidth to create a shaper pipe!') + + self._base_check() + + def reload(self) -> None: + if self.p['reset']: + self.API_CMD_REL = 'flushreload' + + self.b.reload() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_queue.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_queue.py new file mode 100644 index 0000000..10c400a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_queue.py @@ -0,0 +1,84 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Queue(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_queue', + 'del': 'del_queue', + 'set': 'set_queue', + 'search': 'get', + 'toggle': 'toggle_queue', + } + API_KEY_PATH = 'ts.queues.queue' + API_MOD = 'trafficshaper' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'codel_enable', 'codel_ecn_enable', 'pie_enable', 'mask', + 'pipe', 'buckets', 'codel_target', 'codel_interval', 'weight', + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + INT_VALIDATIONS = { + 'weight': {'min': 1, 'max': 100}, + 'buckets': {'min': 1, 'max': 65535}, + 'codel_target': {'min': 1, 'max': 10000}, + 'codel_interval': {'min': 1, 'max': 10000}, + } + FIELDS_TYPING = { + 'bool': ['enabled', 'pie_enable', 'codel_enable', 'codel_ecn_enable'], + 'select': ['mask', 'pipe'], + } + EXIST_ATTR = 'queue' + TIMEOUT = 20.0 # 'get' timeout + SEARCH_ADDITIONAL = { + 'existing_pipes': 'ts.pipes.pipe', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.queue = {} + self.existing_pipes = None + + def check(self) -> None: + self.b.find(match_fields=[self.FIELD_ID]) + + if self.p['state'] == 'present': + if self.p['pipe'] in [None, '']: + self.m.fail_json("You need to provide a 'pipe' to create a shaper queue!") + + if self.p['weight'] in [None, '']: + if not self.exists: + self.m.fail_json("You need to provide 'weight' to create a shaper queue!") + + else: + self.p['weight'] = self.queue['weight'] + + if self.p['state'] == 'present': + self.b.find_single_link( + field='pipe', + existing=self.existing_pipes, + existing_field_id='description', + ) + + self._base_check() + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + entry['pipe'] = self.existing_pipes[entry['pipe']]['description'] + existing.append(entry) + + return existing + + def reload(self) -> None: + if self.p['reset']: + self.API_CMD_REL = 'flushreload' + + self.b.reload() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_rule.py new file mode 100644 index 0000000..9425122 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/shaper_rule.py @@ -0,0 +1,121 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + validate_port_or_range +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Rule(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'add_rule', + 'del': 'del_rule', + 'set': 'set_rule', + 'search': 'get', + 'toggle': 'toggle_rule', + } + API_KEY_PATH = 'ts.rules.rule' + API_MOD = 'trafficshaper' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'target', 'interface', 'interface2', 'protocol', 'max_packet_length', + 'source_invert', 'source_net', 'source_port', 'destination_invert', + 'destination_net', 'destination_port', 'dscp', 'direction', 'sequence', + ] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'max_packet_length': 'iplen', + 'protocol': 'proto', + 'source_port': 'src_port', + 'source_net': 'source', + 'source_invert': 'source_not', + 'destination_net': 'destination', + 'destination_invert': 'destination_not', + 'destination_port': 'dst_port', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'source_invert', 'destination_invert'], + 'list': ['dscp', 'destination_net', 'source_net'], + 'select': [ + 'interface', 'interface2', 'protocol', 'direction', 'target', + ], + } + INT_VALIDATIONS = { + 'sequence': {'min': 1, 'max': 1000000}, + 'max_packet_length': {'min': 2, 'max': 65535}, + } + EXIST_ATTR = 'rule' + TIMEOUT = 20.0 # 'get' timeout + SEARCH_ADDITIONAL = { + 'existing_pipes': 'ts.pipes.pipe', + 'existing_queues': 'ts.queues.queue', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.rule = {} + self.existing_queues = None + self.existing_pipes = None + + def check(self) -> None: + validate_port_or_range(module=self.m, port=self.p['source_port']) + validate_port_or_range(module=self.m, port=self.p['destination_port']) + + if self.p['state'] == 'present': + if self.p['target_pipe'] in [None, ''] and \ + self.p['target_queue'] in [None, '']: + self.m.fail_json( + "You need to provide a 'target_pipe' or 'target_queue' to " + "create a shaper rule!" + ) + + self.b.find(match_fields=[self.FIELD_ID]) + + if self.p['state'] == 'present': + self.b.find_single_link( + field='target_pipe', + existing=self.existing_pipes, + existing_field_id='description', + fail=False, + set_field='target', + ) + + if not hasattr(self.p, 'target') or self.p['target'] is None: + self.b.find_single_link( + field='target_queue', + existing=self.existing_queues, + existing_field_id='description', + set_field='target', + fail=True, + ) + + self._base_check() + + def get_existing(self) -> list: + existing = [] + + for entry in self.b.get_existing(): + entry['target_pipe'], entry['target_queue'] = None, None + target = entry['target'] + + if target in self.existing_pipes: + entry['target_pipe'] = self.existing_pipes[target]['description'] + + elif target in self.existing_queues: + entry['target_queue'] = self.existing_queues[target]['description'] + + entry.pop('target') + + existing.append(entry) + + return existing + + def reload(self) -> None: + if self.p['reset']: + self.API_CMD_REL = 'flushreload' + + self.b.reload() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/snapshot.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/snapshot.py new file mode 100644 index 0000000..fc96d0f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/snapshot.py @@ -0,0 +1,71 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Snapshot(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'search': 'search', + 'activate': 'activate', + } + API_KEY_PATH = None + API_MOD = 'core' + API_CONT = 'snapshots' + FIELDS_CHANGE = [] + FIELDS_ALL = ['name'] + FIELDS_TYPING = {} + EXIST_ATTR = 'snapshot' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.snapshot = {} + + def check(self) -> None: + self._base_check() + + if self.p['activate'] and 'active' in self.snapshot and 'R' not in self.snapshot['active']: + self.activate() + + def _search_call(self) -> dict: + return self.s.get(cnf={ + **self.call_cnf, + 'command': self.CMDS['search'], + })['rows'] + + def _ensure_zfs(self, resp: dict): + if resp['result'] == 'Unsupported root filesystem': + self.m.fail_json('Unsupported => Snapshots require a ZFS filesystem!') + + def create(self) -> dict: + self.r['changed'] = True + + if not self.m.check_mode: + resp = self.s.post({ + **self.call_cnf, + 'command': self.CMDS['add'], + 'data': {'name': self.p['name']}, + }) + if resp['status'] != 'ok': + self._ensure_zfs(resp) + self.m.fail_json(f"Failed creating snapshot '{self.p['name']}'") + + def update(self) -> dict: + pass + + def activate(self) -> dict: + self.r['changed'] = True + + if not self.m.check_mode: + resp = self.s.post({ + **self.call_cnf, + 'command': self.CMDS['activate'], + 'params': self.snapshot['uuid'], + }) + if resp['status'] != 'ok': + self._ensure_zfs(resp) + self.m.fail_json(f"Failed activating snapshot '{self.p['name']}'") diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/syslog.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/syslog.py new file mode 100644 index 0000000..c5ac24c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/syslog.py @@ -0,0 +1,81 @@ +from ipaddress import IPv6Address, IPv4Address, AddressValueError, NetmaskValueError + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_unset, is_valid_domain +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Syslog(BaseModule): + CMDS = { + 'add': 'add_destination', + 'del': 'del_destination', + 'set': 'set_destination', + 'search': 'get', + 'toggle': 'toggle_destination', + } + API_KEY_PATH = 'syslog.destinations.destination' + API_MOD = 'syslog' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'target', 'transport', 'facility', 'program', 'level', 'certificate', + 'port', 'description', + ] + FIELDS_ALL = ['rfc5424', 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'target': 'hostname', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'rfc5424'], + 'list': ['program', 'level', 'facility'], + 'select': ['certificate', 'transport'], + 'int': ['port'], + } + EXIST_ATTR = 'dest' + TIMEOUT = 40.0 # reload using unresolvable dns + INT_VALIDATIONS = { + 'port': {'min': 1, 'max': 65535}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.dest = {} + + def check(self) -> None: + if not is_ip(self.p['target']) and \ + not is_valid_domain(self.p['target']): + self.m.fail_json( + f"Value of target '{self.p['target']}' is neither " + f"a valid IP-Address nor a valid domain-name!" + ) + + if self.p['transport'].startswith('tls') and is_unset(self.p['certificate']): + self.m.fail_json( + "You need to provide a certificate to use encrypted transport!" + ) + + if is_ip(self.p['target']): + if self.p['transport'].find('6') != -1: + try: + IPv6Address(self.p['target']) + + except (AddressValueError, NetmaskValueError): + self.m.fail_json( + "Target does not match transport ip-protocol (IPv6)!" + ) + + else: + try: + IPv4Address(self.p['target']) + + except (AddressValueError, NetmaskValueError): + self.m.fail_json( + "Target does not match transport ip-protocol (IPv4)!" + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_acl.py new file mode 100644 index 0000000..e1b821a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_acl.py @@ -0,0 +1,49 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip_or_network, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Acl(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_acl', + 'del': 'del_acl', + 'set': 'set_acl', + 'search': 'get', + 'toggle': 'toggle_acl', + } + API_KEY_PATH = 'unbound.acls.acl' + API_MOD = 'unbound' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['action', 'networks', 'description'] + FIELDS_ALL = ['enabled', FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['action'], + 'list': ['networks'], + } + STR_VALIDATIONS = { + 'description': r'^.{0,255}$', + } + EXIST_ATTR = 'acl' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.acl = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['networks']): + self.m.fail_json('You need to provide a network(s) to create an ACL!') + + for net in self.p['networks']: + if not is_ip_or_network(net): + self.m.fail_json(f"It seems you provided an invalid network: '{net}'") + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_dnsbl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_dnsbl.py new file mode 100644 index 0000000..61dcd95 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_dnsbl.py @@ -0,0 +1,51 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class DnsBL(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addDnsbl', + 'set': 'setDnsbl', + 'del': 'delDnsbl', + 'search': 'searchDnsbl', + 'detail': 'getDnsbl', + } + API_KEY_PATH = 'blocklist' + API_MOD = 'unbound' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'dnsbl' + FIELDS_CHANGE = [ + 'enabled', 'providers', 'download_urls', 'domains_allow', 'domains_block', 'wildcard_domains_block', + 'source_networks', 'cache_ttl', 'nxdomain_address', 'nxdomain', + ] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'name': 'description', + 'providers': 'type', + 'download_urls': 'lists', + 'nxdomain_address': 'address', + 'domains_allow': 'allowlists', + 'domains_block': 'blocklists', + 'wildcard_domains_block': 'wildcards', + 'source_networks': 'source_nets', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'nxdomain'], + 'int': ['cache_ttl'], + 'list': [ + 'providers', 'download_urls', 'domains_allow', 'domains_block', + 'wildcard_domains_block', 'source_networks', + ], + } + EXIST_ATTR = 'bl' + TIMEOUT = 60.0 # 'reload' timeout + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.bl = {} diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_domain.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_domain.py new file mode 100644 index 0000000..41a207a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_domain.py @@ -0,0 +1,51 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + validate_port, is_ip +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.unbound import \ + validate_domain +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Domain(BaseModule): + CMDS = { + 'add': 'add_domain_override', + 'del': 'del_domain_override', + 'set': 'set_domain_override', + 'search': 'get', + 'toggle': 'toggle_domain_override', + } + API_KEY_PATH = 'unbound.domains.domain' + API_MOD = 'unbound' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['domain', 'server', 'description'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + } + EXIST_ATTR = 'domain' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.domain = {} + + def check(self) -> None: + if self.p['state'] == 'present': + validate_domain(module=self.m, domain=self.p['domain']) + self._validate_server() + + self._base_check() + + def _validate_server(self) -> None: + server = self.p['server'] + + if server.find('@') != -1: + server, port = self.p['server'].split('@', 1) + validate_port(module=self.m, port=port) + + if not is_ip(server): + self.m.fail_json(f"Value '{server}' is not a valid IP-address!") diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_dot.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_dot.py new file mode 100644 index 0000000..5ba0a93 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_dot.py @@ -0,0 +1,74 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, valid_hostname, is_true, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.unbound import \ + validate_domain +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class DnsOverTls(BaseModule): + CMDS = { + 'add': 'addDot', + 'del': 'delDot', + 'set': 'setDot', + 'search': 'get', + 'toggle': 'toggleDot', + } + API_KEY_PATH = 'unbound.dots.dot' + API_MOD = 'unbound' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['domain', 'target', 'port', 'verify'] + FIELDS_ALL = ['type', 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'target': 'server', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'int': ['port'], + } + EXIST_ATTR = 'dot' + INT_VALIDATIONS = { + 'port': {'min': 1, 'max': 65535}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.dot = {} + + def check(self) -> None: + if not is_unset(self.p['domain']): + validate_domain(module=self.m, domain=self.p['domain']) + + if not is_unset(self.p['verify']) and \ + not is_ip(self.p['verify']) and \ + not valid_hostname(self.p['verify']): + self.m.fail_json( + f"Verify-value '{self.p['verify']}' is neither a valid IP-Address " + f"nor a valid hostname!" + ) + + if is_unset(self.p['domain']): + self.b.find(match_fields=['target']) + + else: + self.b.find(match_fields=['domain', 'target']) + + self._base_check() + + def _search_call(self) -> list: + dots = [] + raw = self.b.search() + + if len(raw) > 0: + for uuid, dot in raw.items(): + if is_true(dot['type']['dot']['selected']): + dot.pop('type') + dot['uuid'] = uuid + dots.append(dot) + + return dots diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_forward.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_forward.py new file mode 100644 index 0000000..c45c257 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_forward.py @@ -0,0 +1,65 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.unbound import \ + validate_domain +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_true, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Forward(BaseModule): + CMDS = { + 'add': 'addForward', + 'del': 'delForward', + 'set': 'setForward', + 'search': 'get', + 'toggle': 'toggleForward', + } + API_KEY_PATH = 'unbound.dots.dot' + API_MOD = 'unbound' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['domain', 'target', 'port', 'forward_tcp', 'description'] + FIELDS_ALL = ['type', 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'target': 'server', + 'forward_tcp': 'forward_tcp_upstream', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'forward_tcp'], + 'int': ['port'], + } + STR_LEN_VALIDATIONS = { + 'description': {'min': 0, 'max': 255}, + } + INT_VALIDATIONS = { + 'port': {'min': 1, 'max': 65535}, + } + EXIST_ATTR = 'fwd' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.fwd = {} + + def check(self) -> None: + if not is_unset(self.p['domain']): + validate_domain(module=self.m, domain=self.p['domain']) + + self.b.find(match_fields=['domain', 'target']) + self._base_check() + + def _search_call(self) -> list: + fwds = [] + raw = self.b.search() + + if len(raw) > 0: + for uuid, dot in raw.items(): + if is_true(dot['type']['forward']['selected']): + dot.pop('type') + dot['uuid'] = uuid + fwds.append(dot) + + return fwds diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_general.py new file mode 100644 index 0000000..5546368 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_general.py @@ -0,0 +1,107 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_unset, is_ip6_network +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.unbound import \ + validate_domain +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +# Supported as of OPNsense 23.7 +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'unbound.general' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'unbound' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigureGeneral' + FIELDS_CHANGE = [ + 'enabled', 'port', 'interfaces', 'dnssec', 'dns64', 'dns64_prefix', + 'aaaa_only_mode', 'register_dhcp_leases', 'dhcp_domain', + 'register_dhcp_static_mappings', 'register_ipv6_link_local', + 'register_system_records', 'txt_records', 'flush_dns_cache', 'local_zone_type', + 'outgoing_interfaces', 'wpad', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'interfaces': 'active_interface', + 'dns64_prefix': 'dns64prefix', + 'aaaa_only_mode': 'noarecords', + 'register_dhcp_leases': 'regdhcp', + 'dhcp_domain': 'regdhcpdomain', + 'register_dhcp_static_mappings': 'regdhcpstatic', + 'register_ipv6_link_local': 'noreglladdr6', + 'register_system_records': 'noregrecords', + 'txt_records': 'txtsupport', + 'flush_dns_cache': 'cacheflush', + 'local_zone_type': 'local_zone_type', + 'outgoing_interfaces': 'outgoing_interface', + 'wpad': 'enable_wpad', + } + FIELDS_BOOL_INVERT = ['register_ipv6_link_local', 'register_system_records'] + FIELDS_TYPING = { + 'bool': [ + 'enabled', 'dnssec', 'dns64', 'aaaa_only_mode', 'register_dhcp_leases', + 'register_dhcp_static_mappings', 'register_ipv6_link_local', + 'register_system_records', 'txt_records', 'flush_dns_cache', 'wpad', + ], + 'list': [ + 'interfaces', 'outgoing_interfaces', + ], + 'select': ['local_zone_type'], + 'int': ['port'], + } + SEARCH_ADDITIONAL = { + 'existing_active_interfaces': 'unbound.general.active_interface', + 'existing_outgoing_interfaces': 'unbound.general.outgoing_interface', + } + INT_VALIDATIONS = { + 'port': {'min': 1, 'max': 65535}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + self.existing_active_interfaces = [] + self.existing_outgoing_interfaces = [] + + def check(self) -> None: + self._check_validators() + + if not is_unset(self.p['dhcp_domain']): + validate_domain(module=self.m, domain=self.p['dhcp_domain']) + + if not is_ip6_network(self.p['dns64_prefix']): + self.m.fail_json(f"Value '{self.p['dns64_prefix']}' is an invalid IPv6 network!") + + self.settings = self._search_call() + + if not is_unset(self.p['interfaces']): + if len(self.existing_active_interfaces) == 0: + self.m.fail_json('No available interfaces found!') + + self._validate_interfaces( + existing_interfaces=self.existing_active_interfaces, + referenced_interfaces=self.p['interfaces'], + ) + + if not is_unset(self.p['outgoing_interfaces']): + if len(self.existing_outgoing_interfaces) == 0: + self.m.fail_json('No available outgoing interfaces found!') + + self._validate_interfaces( + existing_interfaces=self.existing_outgoing_interfaces, + referenced_interfaces=self.p['outgoing_interfaces'], + ) + + self._build_diff() + + def _validate_interfaces(self, existing_interfaces: list, referenced_interfaces: list) -> None: + for interface in referenced_interfaces: + if interface not in existing_interfaces: + self.m.fail_json(f"Referenced interface '{interface}' was not found!") diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_host.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_host.py new file mode 100644 index 0000000..920a3d3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_host.py @@ -0,0 +1,107 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + to_digit, simplify_translate +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip4, is_ip6, valid_hostname, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.unbound import \ + validate_domain +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Host(BaseModule): + CMDS = { + 'add': 'add_host_override', + 'del': 'del_host_override', + 'set': 'set_host_override', + 'search': 'get', + 'toggle': 'toggle_host_override', + } + API_KEY = 'host' + API_KEY_PATH = f'unbound.hosts.{API_KEY}' + API_MOD = 'unbound' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'hostname', 'domain', 'record_type', 'prio', 'value', + 'description', + ] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'host' + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['record_type'], + } + FIELDS_TRANSLATE = { + 'record_type': 'rr', + # 'prio': 'mxprio', + # 'value': 'mx', # mx or server + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.host = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['value']): + self.m.fail_json( + "You need to provide a 'value' to create a host-override!" + ) + + validate_domain(module=self.m, domain=self.p['domain']) + + if self.p['record_type'] == 'MX': + if not valid_hostname(self.p['value']): + self.m.fail_json(f"Value '{self.p['value']}' is not a valid hostname!") + + else: + self.p['prio'] = None + + if self.p['state'] == 'present': + if self.p['record_type'] == 'A' and not is_ip4(self.p['value']): + self.m.fail_json(f"Value '{self.p['value']}' is not a valid IPv4-address!") + elif self.p['record_type'] == 'AAAA' and not is_ip6(self.p['value']): + self.m.fail_json(f"Value '{self.p['value']}' is not a valid IPv6-address!") + + self._base_check() + + def _simplify_existing(self, host: dict) -> dict: + simple = simplify_translate( + existing=host, + typing=self.FIELDS_TYPING, + translate=self.FIELDS_TRANSLATE, + ) + + if simple['record_type'] == 'MX': + simple['prio'] = host['mxprio'] + simple['value'] = host['mx'] + + else: + simple['value'] = host['server'] + simple['prio'] = '' + + return simple + + def _build_request(self) -> dict: + data = { + 'enabled': to_digit(self.p['enabled']), + 'hostname': self.p['hostname'], + 'domain': self.p['domain'], + 'rr': self.p['record_type'], # A/AAAA/MX + 'description': self.p['description'], + } + + if self.p['record_type'] == 'MX': + data['mxprio'] = self.p['prio'] + data['mx'] = self.p['value'] + + else: + data['server'] = self.p['value'] + + return { + self.API_KEY: data + } diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_host_alias.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_host_alias.py new file mode 100644 index 0000000..09ac514 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/unbound_host_alias.py @@ -0,0 +1,70 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.unbound import \ + validate_domain +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Alias(BaseModule): + CMDS = { + 'add': 'add_host_alias', + 'del': 'del_host_alias', + 'set': 'set_host_alias', + 'search': 'get', + 'toggle': 'toggle_host_alias', + } + API_KEY_PATH = 'unbound.aliases.alias' + API_MOD = 'unbound' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['target', 'domain', 'alias', 'description'] + FIELDS_ALL = ['enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'alias' + FIELDS_TRANSLATE = { + 'target': 'host', + 'alias': 'hostname', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'select': ['target'], + } + SEARCH_ADDITIONAL = { + 'existing_hosts': 'unbound.hosts.host', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.alias = {} + self.existing_hosts = None + self.target_found = False + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['target']): + self.m.fail_json( + "You need to provide a 'target' if you want to create a host-alias!" + ) + + validate_domain(module=self.m, domain=self.p['domain']) + + self.b.find(match_fields=self.p['match_fields']) + + if self.p['state'] == 'present': + self._find_target() + + if not self.target_found: + self.m.fail_json(f"Alias-target '{self.p['target']}' was not found!") + + self._base_check() + + def _find_target(self) -> None: + if len(self.existing_hosts) > 0: + for uuid, host in self.existing_hosts.items(): + if f"{host['hostname']}.{host['domain']}" == self.p['target']: + self.target_found = True + self.p['target'] = uuid + break diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/user.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/user.py new file mode 100644 index 0000000..9101dba --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/user.py @@ -0,0 +1,77 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_unset, get_key_by_value_from_selection +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class User(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add', + 'del': 'del', + 'set': 'set', + 'search': 'search', + 'detail': 'get', + } + API_KEY_PATH = 'user' + API_MOD = 'auth' + API_CONT = 'user' + FIELDS_CHANGE = [ + 'enabled', 'description', 'email', 'comment', 'landing_page', 'language', 'shell', 'expires', + 'authorized_keys' + ] + FIELDS_TYPING = { + 'bool': ['scrambled_password', 'enabled'], + 'select': ['shell', 'language'], + 'list': ['privilege', 'membership'], + } + FIELDS_TRANSLATE = { + 'authorized_keys': 'authorizedkeys', + 'description': 'descr', + 'enabled': 'disabled', + 'privilege': 'priv', + 'membership': 'group_memberships' + } + FIELDS_BOOL_INVERT = ['enabled'] + FIELDS_DIFF_NO_LOG = ['otp_seed', 'password', 'scrambled_password', 'apikeys'] + FIELDS_ALL = ['name', 'password', 'scrambled_password', 'privilege', 'membership'] + FIELDS_ALL.extend(FIELDS_CHANGE) + EXIST_ATTR = 'user' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.user = {} + + def check(self) -> None: + if ( + self.p['update_password'] == 'always' and + not (is_unset(self.p['password']) and is_unset(self.p['scrambled_password'])) + ): + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['password', 'scrambled_password'] + + self._base_check() + + if not is_unset(self.p['membership']) or self.p['membership'] == []: + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['membership'] + self.p['membership'] = [ + get_key_by_value_from_selection(self.b.raw['group_memberships'], g) + for g in self.p['membership'] + ] + if not is_unset(self.p['privilege']) or self.p['privilege'] == []: + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['privilege'] + + def create(self) -> None: + if is_unset(self.p['password']) and is_unset(self.p['scrambled_password']): + self.p['scrambled_password'] = True + self.b.create() + + def update(self) -> None: + self.b.update(enable_switch=False) + + def delete(self) -> None: + if self.user['scope'] == 'system': + self.m.fail_json(f"Not allowed to delete system user {self.user['name']}") + self.b.delete() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wazuh_agent.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wazuh_agent.py new file mode 100644 index 0000000..7e14642 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wazuh_agent.py @@ -0,0 +1,66 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class WazuhAgent(GeneralModule): + CMDS = { + 'search': 'get', + 'set': 'set', + } + API_KEY_PATH = 'agent' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'wazuhagent' + API_CONT = 'settings' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + + FIELDS_TRANSLATE = { + # General section + 'enabled': ('general', 'enabled'), + 'server_address': ('general', 'server_address'), + 'agent_name': ('general', 'agent_name'), + 'protocol': ('general', 'protocol'), + 'port': ('general', 'port'), + 'debug_level': ('general', 'debug_level'), + # Auth section + 'auth_password': ('auth', 'password'), + 'auth_port': ('auth', 'port'), + # Logcollector section + 'remote_commands': ('logcollector', 'remote_commands'), + 'suricata_eve_log': ('logcollector', 'suricata_eve_log'), + 'syslog_programs': ('logcollector', 'syslog_programs'), + # Module sections + 'rootcheck_enabled': ('rootcheck', 'enabled'), + 'syscollector_enabled': ('syscollector', 'enabled'), + 'syscheck_enabled': ('syscheck', 'enabled'), + 'active_response_enabled': ('active_response', 'enabled'), + 'active_response_remote_commands': ('active_response', 'remote_commands'), + 'active_response_fw_alias_ignore': ('active_response', 'fw_alias_ignore'), + } + + FIELDS_CHANGE = list(FIELDS_TRANSLATE.keys()) + FIELDS_ALL = FIELDS_CHANGE + FIELDS_DIFF_NO_LOG = ['auth_password'] + + FIELDS_TYPING = { + 'bool': [ + 'enabled', 'remote_commands', 'suricata_eve_log', + 'rootcheck_enabled', 'syscollector_enabled', 'syscheck_enabled', + 'active_response_enabled', 'active_response_remote_commands' + ], + 'int': ['port', 'auth_port'], + 'list': ['syslog_programs', 'active_response_fw_alias_ignore'], + 'select': ['protocol'], + 'select_opt_list_idx': ['debug_level'], + } + INT_VALIDATIONS = { + 'port': {'min': 1, 'max': 65535}, + 'auth_port': {'min': 1, 'max': 65535}, + } + + TIMEOUT = 60.0 + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_acl.py new file mode 100644 index 0000000..fb5ddc1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_acl.py @@ -0,0 +1,47 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'proxy.forward.acl' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'allow', 'exclude', 'banned', 'exclude_domains', 'block_domains', + 'block_user_agents', 'block_mime_types', 'exclude_google', 'youtube_filter', + 'ports_tcp', 'ports_ssl', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'allow': 'allowedSubnets', + 'exclude': 'unrestricted', + 'banned': 'bannedHosts', + 'exclude_domains': 'whiteList', + 'block_domains': 'blackList', + 'block_user_agents': 'browser', + 'block_mime_types': 'mimeType', + 'exclude_google': 'googleapps', + 'youtube_filter': 'youtube', + 'ports_tcp': 'safePorts', + 'ports_ssl': 'sslPorts', + } + FIELDS_TYPING = { + 'list': [ + 'allow', 'exclude', 'banned', 'exclude_domains', 'block_domains', + 'block_user_agents', 'block_mime_types', 'exclude_google', + 'ports_tcp', 'ports_ssl', + ], + 'select': ['youtube_filter'] + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_auth.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_auth.py new file mode 100644 index 0000000..7d031e8 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_auth.py @@ -0,0 +1,35 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'proxy.forward.authentication' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['method', 'group', 'prompt', 'ttl_h', 'processes'] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'group': 'authEnforceGroup', + 'prompt': 'realm', + 'ttl_h': 'credentialsttl', + 'processes': 'children', + } + FIELDS_TYPING = { + 'int': ['ttl_h', 'processes'], + 'select': ['method', 'group'], + } + STR_LEN_VALIDATIONS = { + 'prompt': {'min': 0, 'max': 255} + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_cache.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_cache.py new file mode 100644 index 0000000..2569cd6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_cache.py @@ -0,0 +1,43 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class Cache(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'proxy.general.cache.local' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'memory_mb', 'size_mb', 'directory', 'size_mb_max', 'memory_kb_max', 'memory_cache_mode', + 'cache_linux_packages', 'cache_windows_updates', 'swap_timeout', 'max_swap_rate', 'slot_size', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'memory_mb': 'cache_mem', + 'size_mb': 'size', + 'size_mb_max': 'maximum_object_size', + 'memory_kb_max': 'maximum_object_size_in_memory', + } + FIELDS_TYPING = { + 'bool': ['cache_linux_packages', 'cache_windows_updates'], + 'select': ['memory_cache_mode'], + 'int': [ + 'memory_mb', 'size_mb', 'size_mb_max', 'memory_kb_max', 'swap_timeout', 'max_swap_rate', + 'slot_size', + ], + } + INT_VALIDATIONS = { + 'size_mb_max': {'min': 1, 'max': 99999}, + 'memory_kb_max': {'min': 1, 'max': 99999}, + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_forward.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_forward.py new file mode 100644 index 0000000..1ff1908 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_forward.py @@ -0,0 +1,60 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'proxy.forward' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'interfaces', 'port', 'port_ssl', 'transparent', 'ssl_inspection', + 'ssl_inspection_sni_only', 'ssl_ca', 'ssl_exclude', 'ssl_cache_mb', + 'ssl_workers', 'allow_interface_subnets', 'snmp', 'port_snmp', + 'snmp_password', 'interfaces_ftp', 'port_ftp', 'transparent_ftp', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'port_ssl': 'sslbumpport', + 'transparent': 'transparentMode', + 'ssl_inspection': 'sslbump', + 'ssl_inspection_sni_only': 'sslurlonly', + 'ssl_ca': 'sslcertificate', + 'ssl_exclude': 'sslnobumpsites', + 'ssl_cache_mb': 'ssl_crtd_storage_max_size', + 'ssl_workers': 'sslcrtd_children', + 'allow_interface_subnets': 'addACLforInterfaceSubnets', + 'snmp': 'snmp_enable', + 'port_snmp': 'snmp_port', + 'interfaces_ftp': 'ftpInterfaces', + 'port_ftp': 'ftpPort', + 'transparent_ftp': 'ftpTransparentMode', + } + FIELDS_TYPING = { + 'bool': [ + 'transparent_ftp', 'snmp', 'allow_interface_subnets', 'ssl_inspection_sni_only', + 'ssl_inspection', 'transparent', + ], + 'list': ['interfaces', 'ssl_exclude', 'interfaces_ftp'], + 'int': ['port', 'port_ssl', 'ssl_cache_mb', 'ssl_workers', 'port_snmp'], + 'select': ['ssl_ca'], + } + INT_VALIDATIONS = { + 'ssl_workers': {'min': 1, 'max': 32}, + 'ssl_cache_mb': {'min': 1, 'max': 65535}, + 'port': {'min': 1, 'max': 65535}, + 'port_ssl': {'min': 1, 'max': 65535}, + 'port_snmp': {'min': 1, 'max': 65535}, + } + FIELDS_DIFF_EXCLUDE = ['snmp_password'] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_general.py new file mode 100644 index 0000000..9f0f17c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_general.py @@ -0,0 +1,59 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'proxy.general' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'errors', 'icp_port', 'log', 'log_store', 'log_target', 'log_ignore', + 'dns_servers', 'use_via_header', 'handling_forwarded_for', + 'hostname', 'email', 'suppress_version', 'connect_timeout', 'handling_uri_whitespace', + 'pinger', 'enabled', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'errors': 'error_pages', + 'icp_port': 'icpPort', + 'dns_servers': 'alternateDNSservers', + 'handling_forwarded_for': 'forwardedForHandling', + 'handling_uri_whitespace': 'uriWhitespaceHandling', + 'pinger': 'enablePinger', + 'use_via_header': 'useViaHeader', + 'suppress_version': 'suppressVersion', + 'connect_timeout': 'connecttimeout', + 'email': 'VisibleEmail', + 'hostname': 'VisibleHostname', + 'log': ('logging', 'enable', 'accessLog'), + 'log_store': ('logging', 'enable', 'storeLog'), + 'log_target': ('logging', 'target'), + 'log_ignore': ('logging', 'ignoreLogACL'), + } + FIELDS_TYPING = { + 'bool': [ + 'enabled', 'pinger', 'suppress_version', 'use_via_header', 'log', 'log_store', + ], + 'list': ['dns_servers', 'log_ignore'], + 'select': [ + 'errors', 'handling_forwarded_for', 'handling_uri_whitespace', 'log_target' + ], + 'int': ['connect_timeout', 'icp_port'], + } + INT_VALIDATIONS = { + 'connect_timeout': {'min': 1, 'max': 120}, + 'icp_port': {'min': 1, 'max': 65535}, + } + TIMEOUT = 60.0 # 'disable' taking long + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_icap.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_icap.py new file mode 100644 index 0000000..4fc42d3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_icap.py @@ -0,0 +1,46 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class General(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'proxy.forward.icap' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'request_url', 'response_url', 'ttl', 'send_client_ip', 'send_username', + 'encode_username', 'header_username', 'preview', 'preview_size', 'exclude', + 'enabled', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'enabled': 'enable', + 'request_url': 'RequestURL', + 'response_url': 'ResponseURL', + 'ttl': 'OptionsTTL', + 'send_client_ip': 'SendClientIP', + 'send_username': 'SendUsername', + 'encode_username': 'EncodeUsername', + 'header_username': 'UsernameHeader', + 'preview': 'EnablePreview', + 'preview_size': 'PreviewSize', + } + FIELDS_TYPING = { + 'list': ['exclude'], + 'bool': ['enabled', 'send_client_ip', 'send_username', 'encode_username', 'preview'], + 'int': ['ttl', 'preview_size'] + } + STR_VALIDATIONS = { + 'header_username': r'^([a-zA-Z-]+)$' + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_match.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_match.py new file mode 100644 index 0000000..e332abe --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_match.py @@ -0,0 +1,84 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset + + +class Match(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addPACMatch', + 'set': 'setPACMatch', + 'del': 'delPACMatch', + 'search': 'get', + } + API_KEY_PATH = 'proxy.pac.match' + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'negate', 'url', 'description', 'type', 'hostname', 'network', + 'domain_level_from', 'domain_level_to', 'hour_from', 'hour_to', + 'month_from', 'month_to', 'weekday_from', 'weekday_to', + ] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'type': 'match_type', + 'hour_from': 'time_from', + 'hour_to': 'time_to', + 'month_from': 'date_from', + 'month_to': 'date_to', + } + FIELDS_TYPING = { + 'select': ['type', 'month_from', 'month_to', 'weekday_from', 'weekday_to'], + 'bool': ['negate'], + 'int': [ + 'domain_level_from', 'domain_level_to', 'hour_from', 'hour_to', 'month_from', + 'month_to', 'weekday_from', 'weekday_to', + ], + } + INT_VALIDATIONS = { + 'hour_from': {'min': 0, 'max': 23}, + 'hour_to': {'min': 0, 'max': 23}, + } + EXIST_ATTR = 'proxy' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.proxy = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if self.p['type'] == 'url_matches': + if is_unset(self.p['url']): + self.m.fail_json('You need to provide an URL to match!') + + if self.p['type'] in [ + 'hostname_matches', 'plain_hostname', 'is_resolvable', 'dns_domain_is', + ] and is_unset(self.p['hostname']): + self.m.fail_json('You need to provide a hostname to match!') + + if self.p['type'] in ['my_ip_in_net', 'destination_in_net'] and \ + is_unset(self.p['network']): + self.m.fail_json('You need to provide a network to match!') + + from_to_fields = { + 'date_range': {'from': 'month_from', 'to': 'month_to'}, + 'time_range': {'from': 'hour_from', 'to': 'hour_to'}, + 'weekday_range': {'from': 'weekday_from', 'to': 'weekday_to'}, + 'dns_domain_levels': {'from': 'domain_level_from', 'to': 'domain_level_to'}, + } + + for match_type, from_to in from_to_fields.items(): + if self.p['type'] == match_type and ( + is_unset(self.p[from_to['from']]) or is_unset(from_to['to']) + ): + self.m.fail_json( + f"You need to provide a '{from_to['from']}' and '{from_to['to']}' " + 'to match!' + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_proxy.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_proxy.py new file mode 100644 index 0000000..856e759 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_proxy.py @@ -0,0 +1,41 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset + + +class Proxy(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'addPACProxy', + 'set': 'setPACProxy', + 'del': 'delPACProxy', + 'search': 'get', + } + API_KEY_PATH = 'proxy.pac.proxy' + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['type', 'url', 'description'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'type': 'proxy_type', + } + FIELDS_TYPING = { + 'select': ['type'], + } + EXIST_ATTR = 'proxy' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.proxy = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['url']): + self.m.fail_json('You need to provide an URL to create a PAC-proxy!') + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_rule.py new file mode 100644 index 0000000..53c4d29 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_pac_rule.py @@ -0,0 +1,59 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset + + +class Rule(BaseModule): + FIELD_ID = 'description' + CMDS = { + 'add': 'addPACRule', + 'set': 'setPACRule', + 'del': 'delPACRule', + 'search': 'get', + } + API_KEY_PATH = 'proxy.pac.rule' + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = ['matches', 'proxies', 'join_type', 'match_type'] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'list': ['matches', 'proxies'], + 'select': ['join_type', 'match_type'], + } + EXIST_ATTR = 'rule' + SEARCH_ADDITIONAL = { + 'existing_matches': 'proxy.pac.match', + 'existing_proxies': 'proxy.pac.proxy', + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.rule = {} + self.existing_matches = {} + self.existing_proxies = {} + + def check(self) -> None: + if self.p['state'] == 'present' and \ + (is_unset(self.p['proxies']) or is_unset(self.p['matches'])): + self.m.fail_json( + 'You need to provide at least one proxy and match to create a rule!' + ) + + self.b.find(match_fields=[self.FIELD_ID]) + + if self.p['state'] == 'present': + self.b.find_multiple_links( + field='matches', + existing=self.existing_matches, + ) + self.b.find_multiple_links( + field='proxies', + existing=self.existing_proxies, + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_parent.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_parent.py new file mode 100644 index 0000000..48cc87e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_parent.py @@ -0,0 +1,55 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class Parent(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'proxy.general.parentproxy' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'host', 'auth', 'user', 'password', 'port', 'local_domains', 'local_ips', + 'enabled', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'auth': 'enableauth', + 'local_domains': 'localdomains', + 'local_ips': 'localips', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'auth'], + 'list': ['local_domains', 'local_ips'], + 'int': ['port'], + } + INT_VALIDATIONS = { + 'port': {'min': 1, 'max': 65535}, + } + STR_VALIDATIONS = { + 'user': r'^([0-9a-zA-Z\._\-]){1,32}$', + 'password': r'^([0-9a-zA-Z\._\-]){1,32}$', + } + FIELDS_DIFF_NO_LOG = ['password'] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) + + def check(self) -> None: + if self.p['enabled']: + if is_unset(self.p['host']) or is_unset(self.p['port']): + self.m.fail_json('To enable a parent proxy, a host and port must be provided!') + + if not is_ip(self.p['host']): + self.m.fail_json(f"Provided host '{self.p['host']}' is not a valid IP-Address!") + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_remote_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_remote_acl.py new file mode 100644 index 0000000..f87cb22 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_remote_acl.py @@ -0,0 +1,68 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_valid_url + + +class Acl(BaseModule): + FIELD_ID = 'file' + CMDS = { + 'add': 'addRemoteBlacklist', + 'set': 'setRemoteBlacklist', + 'del': 'delRemoteBlacklist', + 'search': 'get', + 'toggle': 'toggleRemoteBlacklist', + } + API_KEY_PATH = 'proxy.forward.acl.remoteACLs.blacklists.blacklist' + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'url', 'username', 'password', 'categories', 'verify_ssl', + 'description', + ] + FIELDS_ALL = [FIELD_ID] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'file': 'filename', + 'categories': 'filter', + 'verify_ssl': 'sslNoVerify', + } + FIELDS_BOOL_INVERT = ['verify_ssl'] + FIELDS_TYPING = { + 'list': ['categories'], + 'bool': ['enabled', 'verify_ssl'], + } + STR_VALIDATIONS = { + 'file': r'^[a-zA-Z0-9]{1,245}\.?[a-zA-z0-9]{1,10}$' + } + EXIST_ATTR = 'acl' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.acl = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['url']): + self.m.fail_json('You need to provide an URL to create a remote ACL!') + + if not is_valid_url(self.p['url']): + self.m.fail_json(f"The provided URL seems to be invalid: '{self.p['url']}'") + + if is_unset(self.p['description']): + self.m.fail_json('You need to provide a description to create a remote ACL!') + + creds = [not is_unset(self.p['username']), not is_unset(self.p['password'])] + + if not all(creds) and any(creds): + self.m.fail_json( + "You need to provide both 'username' and 'password' for " + "authentication to work!" + ) + + self._base_check() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_traffic.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_traffic.py new file mode 100644 index 0000000..4bfad98 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/webproxy_traffic.py @@ -0,0 +1,38 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import GeneralModule + + +class Traffic(GeneralModule): + CMDS = { + 'set': 'set', + 'search': 'get', + } + API_KEY_PATH = 'proxy.general.traffic' + API_KEY_PATH_REQ = API_KEY_PATH + API_MOD = 'proxy' + API_CONT = 'settings' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'download_kb_max', 'upload_kb_max', 'throttle_kb_bandwidth', + 'throttle_kb_host_bandwidth', 'enabled', + ] + FIELDS_ALL = FIELDS_CHANGE + FIELDS_TRANSLATE = { + 'download_kb_max': 'maxDownloadSize', + 'upload_kb_max': 'maxUploadSize', + 'throttle_kb_bandwidth': 'OverallBandwidthTrotteling', + 'throttle_kb_host_bandwidth': 'perHostTrotteling', + } + FIELDS_TYPING = { + 'int': [ + 'download_kb_max', 'upload_kb_max', 'throttle_kb_bandwidth', + 'throttle_kb_host_bandwidth', + ], + 'bool': ['enabled'] + } + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + GeneralModule.__init__(self=self, m=module, r=result, s=session) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wireguard_peer.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wireguard_peer.py new file mode 100644 index 0000000..3dd87b3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wireguard_peer.py @@ -0,0 +1,130 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_ip_or_network, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_valid_domain + + +class Peer(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_client', + 'del': 'del_client', + 'set': 'set_client', + 'search': 'search_client', + 'detail': 'get_client', + 'toggle': 'toggle_client', + } + API_KEY_PATH = 'client' + API_MOD = 'wireguard' + API_CONT = 'client' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'public_key', 'psk', 'port', 'allowed_ips', 'server', 'keepalive', 'servers', + ] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'public_key': 'pubkey', + 'allowed_ips': 'tunneladdress', + 'server': 'serveraddress', + 'port': 'serverport', + } + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': ['allowed_ips', 'servers'], + 'int': ['port', 'keepalive'], + } + FIELDS_DIFF_NO_LOG = ['psk'] + INT_VALIDATIONS = { + 'keepalive': {'min': 1, 'max': 86400}, + 'port': {'min': 1, 'max': 65535}, + } + STR_VALIDATIONS = { + 'name': r'^([0-9a-zA-Z._\-]){1,64}$' + } + EXIST_ATTR = 'peer' + FIELDS_DIFF_EXCLUDE = [] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.peer = {} + self.existing_servers = None + self.existing_peers = None + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['public_key']): + self.m.fail_json( + "You need to provide a 'public_key' if you want to create a peer!" + ) + + if is_unset(self.p['allowed_ips']): + self.m.fail_json( + "You need to provide at least one 'allowed_ips' entry " + "of the peer to create!" + ) + + link_servers = not is_unset(self.p['servers']) or self.p['link_servers'] + if not link_servers: + self.FIELDS_CHANGE.remove('servers') + self.FIELDS_DIFF_EXCLUDE.append('servers') + + for entry in self.p['allowed_ips']: + if not is_ip_or_network(entry): + self.m.fail_json( + f"Allowed-ip entry '{entry}' is neither a valid IP-address " + f"nor a valid network!" + ) + + if not is_unset(self.p['server']) and \ + not is_ip(self.p['server']) and not is_valid_domain(self.p['server']): + self.m.fail_json( + f"Peer endpoint/server '{self.p['server']}' is neither a valid IP-address " + f"nor a valid domain!" + ) + + self.b.find(match_fields=[self.FIELD_ID]) + if self.p['state'] == 'present' and not is_unset(self.p['servers']): + self.p['servers'] = self._translate_servers(self.p['servers']) + + if self.exists: + if link_servers: + self.peer['servers'] = self._translate_servers(self.r['diff']['before']['servers']) + + self.r['diff']['before'] = self.b.build_diff(data=self.peer) + + self._base_check() + + def _translate_servers(self, search_in: list) -> list: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wireguard_server import Server + + servers = [] + existing = {} + + if self.existing_servers is None: + self.existing_servers = Server( + module=self.m, result={}, session=self.s + ).get_existing() + + if len(search_in) == 0: + return [] + + for srv in self.existing_servers: + existing[srv['name']] = srv['uuid'] + + for srv in search_in: + if srv not in existing and srv not in existing.values(): + self.m.fail_json(f"Server '{srv}' does not exist!") + + if srv in existing: + servers.append(existing[srv]) + + else: + servers.append(srv) + + return servers diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wireguard_server.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wireguard_server.py new file mode 100644 index 0000000..fc2acf4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/module_utils/main/wireguard_server.py @@ -0,0 +1,164 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.validate import \ + is_ip, is_ip_or_network, is_unset +from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wireguard_peer import Peer +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.cls import BaseModule + + +class Server(BaseModule): + FIELD_ID = 'name' + CMDS = { + 'add': 'add_server', + 'del': 'del_server', + 'set': 'set_server', + 'search': 'get', + 'detail': 'get_server', + 'toggle': 'toggle_server', + } + API_KEY = 'server' + API_KEY_PATH = f'server.servers.{API_KEY}' + API_MOD = 'wireguard' + API_CONT = 'server' + API_CONT_REL = 'service' + FIELDS_CHANGE = [ + 'public_key', 'private_key', 'port', 'mtu', 'dns_servers', 'allowed_ips', + 'disable_routes', 'gateway', 'peers', 'vip', + ] + FIELDS_ALL = [FIELD_ID, 'enabled'] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'dns_servers': 'dns', + 'public_key': 'pubkey', + 'private_key': 'privkey', + 'allowed_ips': 'tunneladdress', + 'disable_routes': 'disableroutes', + 'vip': 'carp_depend_on', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'disable_routes'], + 'list': ['dns_servers', 'allowed_ips', 'peers'], + 'int': ['port', 'mtu', 'instance'], + 'select' : ['vip'], + } + FIELDS_DIFF_NO_LOG = ['private_key'] + INT_VALIDATIONS = { + 'mtu': {'min': 1, 'max': 9300}, + 'port': {'min': 1, 'max': 65535}, + } + STR_VALIDATIONS = { + 'name': r'^([0-9a-zA-Z._\-]){1,64}$' + } + EXIST_ATTR = 'server' + FIELDS_DIFF_EXCLUDE = [] + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None, fail: dict = None): + BaseModule.__init__(self=self, m=module, r=result, s=session, f=fail) + self.server = {} + self.existing_peers = None + self.existing_vips = {} + + def check(self) -> None: + if self.p['state'] == 'present': + if is_unset(self.p['allowed_ips']): + self.m.fail_json( + "You need to provide at least one 'allowed_ips' entry " + "to create a server!" + ) + + if not is_unset(self.p['gateway']) and not is_ip(self.p['gateway']): + self.m.fail_json( + f"Gateway '{self.p['gateway']}' is not a valid IP-address!" + ) + + if is_unset(self.p['private_key']): + self.m.fail_json( + "You need to provide a 'private_key'!" + ) + + link_peers = not is_unset(self.p['peers']) or self.p['link_peers'] + if not link_peers: + self.FIELDS_CHANGE.remove('peers') + self.FIELDS_DIFF_EXCLUDE.append('peers') + + for entry in self.p['allowed_ips']: + if not is_ip_or_network(entry): + self.m.fail_json( + f"Allowed-ip entry '{entry}' is neither a valid IP-address " + f"nor a valid network!" + ) + + for dns in self.p['dns_servers']: + if not is_ip(dns): + self.m.fail_json(f"DNS-value '{dns}' is not a valid IP-address!") + + self.b.find(match_fields=[self.FIELD_ID]) + if self.exists: + if is_unset(self.p['public_key']) or is_unset(self.p['private_key']): + self.p['public_key'] = self.server['public_key'] + self.p['private_key'] = self.server['private_key'] + + if self.p['state'] == 'present': + if link_peers: + self.p['peers'] = self._find_peers() + + if not is_unset(self.p['vip']): + self.p['vip'] = self._find_vip() + + self._base_check() + + def _find_peers(self) -> list: + peers = [] + existing = {} + + if self.existing_peers is None: + self.existing_peers = Peer( + module=self.m, result={}, session=self.s + ).get_existing() + + if len(self.p['peers']) == 0: + return [] + + for peer in self.existing_peers: + existing[peer['name']] = peer['uuid'] + + for peer in self.p['peers']: + if peer not in existing and peer not in existing.values(): + self.m.fail_json(f"Peer '{peer}' does not exist!") + + if peer in existing: + peers.append(existing[peer]) + + else: + peers.append(peer) + + return peers + + def _find_vip(self) -> str: + # "[192.168.1.1] on opt1 (vhid 1)" + search_vip = f"[{self.p['vip']}]" + existing_vips = [] + + for uuid, values in self.existing_vips.items(): + if values['value'].find(search_vip) != -1: + return uuid + + if values['value'].find('[') != -1: + existing_vips.append(values['value'].split('[', 1)[1].split(']')[0]) + + self.m.fail_json(f"Provided VIP '{self.p['vip']}' was not found! Existing ones: {existing_vips}") + + def _search_call(self) -> list: + raw = self.b.search() + if len(raw) > 0: + self.existing_vips = raw[list(raw.keys())[0]][self.FIELDS_TRANSLATE['vip']] + + if len(raw) == 0: + self.existing_vips = self.s.get(cnf={ + **self.call_cnf, + 'command': self.CMDS['detail'], + })[self.API_KEY][self.FIELDS_TRANSLATE['vip']] + + return raw diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_account.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_account.py new file mode 100644 index 0000000..c48ee92 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_account.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_account import Account + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this account.', + ), + description=dict( + type='str', required=False, aliases=['desc'], + description='Description for this account.', + ), + email=dict( + type='str', required=False, + description='E-mail address for this account.', + ), + ca=dict( + type='str', required=False, default='letsencrypt', + choices=[ + 'buypass', 'buypass_test', 'google', 'google_test', 'letsencrypt', 'letsencrypt_test', 'sslcom', + 'zerossl', 'custom', + ], + ), + custom_ca=dict( + type='str', required=False, + description='The HTTPS URL of the custom ACME CA that should be used for this account and all associated ' + 'certificates. For example: https://ca.internal/acme/directory' + ), + eab_kid=dict( + type='str', required=False, + description='An value provided by the CA when using ACME External Account Binding (EAB).', + ), + eab_hmac=dict( + type='str', required=False, + description='An value provided by the CA when using ACME External Account Binding (EAB).', + ), + register=dict( + type='bool', required=False, default=False, + description='Register the selected account with the configured ACME CA', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('ca', 'custom', ('custom_ca',)), + ], + ) + + module_wrapper(Account(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_action.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_action.py new file mode 100644 index 0000000..9b45a81 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_action.py @@ -0,0 +1,296 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_action import Action + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this automation.', + ), + description=dict( + type='str', required=False, aliases=['desc'], + description='Description for this automation. ', + ), + type=dict( + type='str', required=False, + choices=[ + 'configd_restart_gui', 'configd_restart_haproxy', 'configd_restart_nginx','configd_upload_sftp', + 'configd_remote_ssh', 'acme_fritzbox', 'acme_panos', 'acme_proxmoxve', 'acme_vault', + 'acme_synology_dsm', 'acme_truenas', 'acme_unifi', 'configd_generic', + ], + ), + sftp_host=dict( + type='str', required=False, + description='IP address or hostname of the SFTP server.' + ), + sftp_host_key=dict( + type='str', required=False, + description='SFTP server host key, formatted as in \'known_hosts\'. Leave blank to auto accept host key ' + 'on first connect (not as secure as specifying it).' + ), + sftp_port=dict( + type='int', required=False, defalt=22, + description='SFTP server port. Leave blank to use default "22".' + ), + sftp_user=dict( + type='str', required=False, + description='The username to login to the SFTP server.' + ), + sftp_identity_type=dict( + type='str', required=False, + choices=['ecdsa', 'rsa', 'ed25519'], + description='The type of identify to present to the SFTP server for authorization.' + ), + sftp_remote_path=dict( + type='str', required=False, + description='Path on the SFTP server to change to after login. The path can be absolute or relative to ' + 'home and must exist. Leave blank to not change path after login.' + ), + sftp_chgrp=dict( + type='str', required=False, + description='Unix group id to apply to all uploaded files. Leave blank to not change the group.' + ), + sftp_chmod=dict( + type='str', required=False, + description='Unix permission to apply to uploaded public keys. Leave blank to use default "0440".' + ), + sftp_chmod_key=dict( + type='str', required=False, + description='Unix permission to apply to uploaded private keys. Leave blank to use default "0400".' + ), + sftp_filename_cert=dict( + type='str', required=False, + description='Name template for the public certificate. Placeholders "{{name}}" and "%s" are replaced by ' + 'the name of the certificate being uploaded. Leave blank to use default "{{name}}/cert.pem".' + ), + sftp_filename_key=dict( + type='str', required=False, + description='Name template for the certificate\'s private key. Placeholders "{{name}}" and "%s" are ' + 'replaced by the name of the certificate being uploaded. Leave blank to use default ' + '"{{name}}/key.pem".' + ), + sftp_filename_ca=dict( + type='str', required=False, + description='Name template for the public certificate chain file. Placeholders "{{name}}" and "%s" are ' + 'replaced by the name of the certificate being uploaded. Leave blank to use default ' + '"{{name}}/ca.pem".' + ), + sftp_filename_fullchain=dict( + type='str', required=False, + description='Name template for the public certificate fullchain file (cert + ca). Placeholders "{{name}}" ' + 'and "%s" are replaced by the name of the certificate being uploaded. Leave blank to use ' + 'default "{{name}}/fullchain.pem".' + ), + # Remote SSH + remote_ssh_host=dict( + type='str', required=False, + description='IP address or hostname of the SSH server.' + ), + remote_ssh_host_key=dict( + type='str', required=False, + description='SSH server host key, formatted as in \'known_hosts\'. Leave blank to auto accept host key on ' + 'first connect (not as secure as specifying it).' + ), + remote_ssh_port=dict( + type='int', required=False, defalt=22, + description='SSH server port. Leave blank to use default "22".' + ), + remote_ssh_user=dict( + type='str', required=False, + description='The username to login to the SSH server.' + ), + remote_ssh_identity_type=dict( + type='str', required=False, + choices=['ecdsa', 'rsa', 'ed25519'], + description='The type of identify to present to the SSH server for authorization.' + ), + remote_ssh_command=dict( + type='str', required=False, + description='The command to execute on the SSH server.' + ), + # Configd + configd_generic_command=dict( + type='str', required=False, + description='Select a pre-defined system command which should be run.' + ), + # ACME FRITZ!Box + acme_fritzbox_url=dict( + type='str', required=False, + description='URL of the router, i.e. https://fritzbox.example.com.' + ), + acme_fritzbox_username=dict( + type='str', required=False, + description='The username to login to the router.' + ), + acme_fritzbox_password=dict( + type='str', required=False, no_log=True, + description='The password to login to the router.' + ), + # ACME PANOS + acme_panos_username=dict( + type='str', required=False, + description='The username to login to the firewall.' + ), + acme_panos_password=dict( + type='str', required=False, no_log=True, + description='The password to login to the firewall.' + ), + acme_panos_host=dict( + type='str', required=False, + description='The hostname of the router.' + ), + # ACME Proxmox + acme_proxmoxve_user=dict( + type='str', required=False, default='root', + description='The user who owns the API key. Defaults to root.' + ), + acme_proxmoxve_server=dict( + type='str', required=False, + description='The hostname of the proxmox ve node.' + ), + acme_proxmoxve_port=dict( + type='int', required=False, default=8006, + description='The port number the management interface is on. Defaults to 8006.' + ), + acme_proxmoxve_nodename=dict( + type='str', required=False, + description='The name of the node we will be connecting to.' + ), + acme_proxmoxve_realm=dict( + type='str', required=False, default='pam', + description='The authentication realm the user authenticates with. Defaults to pam.' + ), + acme_proxmoxve_tokenid=dict( + type='str', required=False, default='acme', + description='The name of the API token created for the user account. Defaults to acme.' + ), + acme_proxmoxve_tokenkey=dict( + type='str', required=False, no_log=True, + description='The API token.' + ), + # ACME Vault + acme_vault_url=dict( + type='str', required=False, + description='URL of the Vault, i.e. http://vault.example.com:8200.' + ), + acme_vault_prefix=dict( + type='str', required=False, default='acme', + description='This specifies the prefix path in Vault. If you select KV v2 you need to add .../data/... ' + 'between the secret-mount-path and the path. Example: v1 prefix path: secret/acme, v2 prefix ' + 'path: secret/data/acme.' + ), + acme_vault_token=dict( + type='str', required=False, no_log=True, + description='This specifies the Vault token to authenticate with.' + ), + acme_vault_kvv2=dict( + type='bool', required=False, default=True, + description='If checked version 2 of the kv store will be used, otherwise version 1.' + ), + # ACME Synology DSM + acme_synology_dsm_hostname=dict( + type='str', required=False, + description='Hostname of IP adress of the Synology DSM, i.e. synology.example.com or 192.168.0.1.' + ), + acme_synology_dsm_port=dict( + type='int', required=False, default=5000, + description='Port that will be used when connecting to Synology DSM.' + ), + acme_synology_dsm_scheme=dict( + type='str', required=False, default='http', + choices=['http', 'https'], + description='Connection scheme that will be used when uploading certificates to Synology DSM.' + ), + acme_synology_dsm_username=dict( + type='str', required=False, + description='Username to login, must be an administrator.' + ), + acme_synology_dsm_password=dict( + type='str', required=False, no_log=True, + description='Password to login with.' + ), + acme_synology_dsm_create=dict( + type='bool', required=False, default=True, + description='This option ensures that a new certificate is created in Synology DSM if it does not exist ' + 'yet. If unchecked only existing certificates will be updated.' + ), + acme_synology_dsm_deviceid=dict( + type='str', required=False, + description='If Synology DSM has OTP enabled, then the device ID has to be provided so that no OTP is ' + 'required when running the automation.' + ), + acme_synology_dsm_devicename=dict( + type='str', required=False, + description='If Synology DSM has OTP enabled, then the device name has to be provided so that no OTP is ' + 'required when running the automation.' + ), + # ACME TrueNAS + acme_truenas_apikey=dict( + type='str', required=False, no_log=True, + description='API key generated in the TrueNAS web UI.' + ), + acme_truenas_hostname=dict( + type='str', required=False, + description='Hostname or IP adress of TrueNAS Core Server.' + ), + acme_truenas_scheme=dict( + type='str', required=False, default='http', + choices=['http', 'https'], + description='Connection scheme that will be used when uploading certificates to TrueNAS Core Server.' + ), + # ACME unifi + acme_unifi_keystore=dict( + type='str', required=False, default='/usr/local/share/java/unifi/data/keystore', + description='Path to the Unifi keystore file in the local filesystem, i.e. ' + '/usr/local/share/java/unifi/data/keystore.' + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Action(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_certificate.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_certificate.py new file mode 100644 index 0000000..49ab3ca --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_certificate.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_certificate import Certificate + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=False, aliases=['cn'], + description='Common Name (CN) and first Alt Name (subjectAltName) for this certificate.', + ), + description=dict( + type='str', required=True, aliases=['desc'], + description='Description for this certificate.', + ), + alt_names=dict( + type='list', required=False, elements='str', default=[], aliases=['subject_alt_name'], + description='Configure additional names that should be part of the certificate, i.e. www.example.com or ' + 'mail.example.com. Use TAB key to complete typing a FQDN.', + ), + account=dict(type='str', required=False), + validation=dict(type='str', required=False), + auto_renewal=dict( + type='bool', required=False, default=True, + description='Enable automatic renewal for this certificate to prevent expiration. When disabled, the cron ' + 'job will ignore this certificate.', + ), + renew_interval=dict( + type='int', required=False, default=60, + description='Specifies the days to renew the cert. The max value is 5000 days.', + ), + key_length=dict( + type='str', required=False, default='key_4096', + choices=['key_2048', 'key_3072', 'key_4096', 'key_ec256', 'key_ec384'], + description='Specify the domain key length: key_2048, key_3072, key_4096, key_ec256 or key_ec384.', + ), + ocsp=dict( + type='bool', required=False, default=False, + description='Generate and add OCSP Must Staple extension to the certificate.', + ), + restart_actions=dict( + type='list', required=False, elements='str', default=[], + description='Choose the automations that should be run after certificate creation and renewal.', + ), + aliasmode=dict( + type='str', required=False, default='none', + choices=['none', 'automatic', 'domain', 'challenge'], + description='Configure DNS alias mode to validate the certificate.', + ), + domainalias=dict( + type='str', required=False, + description='When setting DNS alias mode to "Domain Alias", enter the domain name that should be used for ' + 'certificate validation. Please refer to the acme.sh documentation for further information. ', + ), + challengealias=dict( + type='str', required=False, + description='When setting DNS alias mode to "Challenge Alias", enter the domain name that should be used ' + 'for certificate validation. Please refer to the acme.sh documentation for further ' + 'information.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('aliasmode', 'domain', ('domainalias',)), + ('aliasmode', 'challenge', ('challengealias',)), + ], + ) + + module_wrapper(Certificate(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_general.py new file mode 100644 index 0000000..9666d05 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_general.py @@ -0,0 +1,88 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' + + +def run_module(): + module_args = dict( + auto_renewal=dict( + type='bool', required=False, default=True, + description='Enable automatic renewal for certificates to prevent expiration. This will add a cron job ' + 'to the system.', + ), + challenge_port=dict( + type='int', required=False, default=43580, + description='When using HTTP-01 as challenge type, a local webserver is used to provide acme challenge ' + 'data to the ACME CA. The local webserver is NOT directly exposed to the outside and should ' + 'NOT use port 80 or any other well-known port. This setting allows you to change the local ' + 'port of this webserver in case it interferes with another local service.', + ), + tls_challenge_port=dict( + type='int', required=False, default=43581, + description='The service port when using TLS-ALPN-01 as challenge type. It works similar to the HTTP-01 ' + 'challenge type.', + ), + restart_timeout=dict( + type='int', required=False, default=600, + description='The maximum time in seconds to wait for an automation to complete. When the timeout is ' + 'reached the command is forcefully aborted.', + ), + haproxy_integration=dict( + type='bool', required=False, default=False, + description='Enable automatic integration with the OPNsense HAProxy plugin.', + ), + log_level=dict( + type='str', required=False, default='normal', + choices=['normal', 'extended', 'debug', 'debug2', 'debug3'], + description='Specifies the log level for acme.sh.', + ), + show_intro=dict( + type='bool', required=False, default=True, + description='Disable to hide all introduction pages.', + ), + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_validation.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_validation.py new file mode 100644 index 0000000..0473475 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/acme_validation.py @@ -0,0 +1,363 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_validation import Validation + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/en/latest/modules/acmeclient.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this validation.', + ), + description=dict( + type='str', required=False, aliases=['desc'], + description='Description for this validation.', + ), + method=dict( + type='str', required=False, default='dns01', + choices=['http01', 'dns01', 'tlsalpn01'], + description='Set the ACME challenge type.', + ), + http_service=dict( + type='str', required=False, default='opnsense', + choices=['opnsense', 'haproxy'], + description='HTTP Service to use for \'http01\' validation.', + ), + http_opn_autodiscovery=dict( + type='bool', required=False, default=True, + description='Choose this option to let OPNsense try to auto-discover these IP addresses.', + ), + http_opn_interface=dict( + type='str', required=False, + description='Choose the interface where this IP address is currently configured.', + ), + http_opn_ipaddresses=dict( + type='list', required=False, elements='str', default=[], + description='Enter the all of these IP addresses here.', + ), + http_haproxy_inject=dict( + type='bool', required=False, default=True, + description='Automatically inject config into the local HAProxy instance to let it serve acme challanges ' + 'without service interruption. Of course, adding the configuration requires a short restart ' + 'of the HAProxy service.', + ), + http_haproxy_frontends=dict( + type='list', required=False, elements='str', default=[], + description='Choose the local HAProxy frontends. They will automatically be configured to redirect acme ' + 'challenges to the internal acme client. The HAProxy service will automatically be restarted ' + 'if a certificate was renewed. ', + ), + tlsalpn_acme_autodiscovery=dict( + type='bool', required=False, default=True, + description='Choose this option to let OPNsense try to auto-discover these IP addresses.', + ), + tlsalpn_acme_interface=dict( + type='str', required=False, + description='Choose the interface where this IP address is currently configured.', + ), + tlsalpn_acme_ipaddresses=dict( + type='list', required=False, elements='str', default=[], + description='Enter the all of these IP addresses here.', + ), + dns_service=dict( + type='str', required=False, default='dns_freedns', + choices=[ + 'dns_1984hosting', 'dns_acmedns', 'dns_acmeproxy', 'dns_active24', 'dns_ad', 'dns_ali', 'dns_kas', + 'dns_arvan', 'dns_artfiles', 'dns_aurora', 'dns_autodns', 'dns_aws', 'dns_azure', 'dns_bunny', + 'dns_cloudns', 'dns_cf', 'dns_cx', 'dns_cn', 'dns_conoha', 'dns_constellix', 'dns_cpanel', 'dns_cyon', + 'dns_ddnss', 'dns_desec', 'dns_dgon', 'dns_da', 'dns_dnsexit', 'dns_dnshome', 'dns_dnsimple', + 'dns_dnsservices', 'dns_domeneshop', 'dns_me', 'dns_dp', 'dns_doapi', 'dns_do', 'dns_dreamhost', + 'dns_duckdns', 'dns_dyn', 'dns_dynu', 'dns_dynv6', 'dns_easydns', 'dns_euserv', 'dns_exoscale', + 'dns_fornex', 'dns_freedns', 'dns_gandi_livedns', 'dns_gd', 'dns_gcloud', 'dns_googledomains', + 'dns_gdnsdk', 'dns_hetzner', 'dns_hexonet', 'dns_hostingde', 'dns_he', 'dns_infoblox', + 'dns_infomaniak', 'dns_internetbs', 'dns_inwx', 'dns_ionos', 'dns_ipv64', 'dns_ispconfig', 'dns_jd', + 'dns_joker', 'dns_kinghost', 'dns_knot', 'dns_leaseweb', 'dns_lexicon', 'dns_limacity', 'dns_linode', + 'dns_linode_v4', 'dns_loopia', 'dns_lua', 'dns_miab', 'dns_mydnsjp', 'dns_mythic_beasts', + 'dns_namecom', 'dns_namecheap', 'dns_namesilo', 'dns_nederhost', 'dns_netcup', 'dns_nic', + 'dns_njalla', 'dns_nsone', 'dns_nsupdate', 'dns_online', 'dns_opnsense', 'dns_oci', 'dns_ovh', + 'dns_pdns', 'dns_pleskxml', 'dns_pointhq', 'dns_porkbun', 'dns_rackspace', 'dns_rage4', 'dns_regru', + 'dns_schlundtech', 'dns_selectel', 'dns_selfhost', 'dns_servercow', 'dns_simply', 'dns_transip', + 'dns_udr', 'dns_unoeuro', 'dns_variomedia', 'dns_vscale', 'dns_vultr', 'dns_world4you', 'dns_yandex', + 'dns_zilore', 'dns_zone', 'dns_zonomi' + ], + ), + dns_sleep=dict( + type='int', required=False, default=0, + description='The time in seconds to wait for all the TXT records to take effect after adding them to the ' + 'DNS API. Defaults to 0 seconds, which causes Acme Client to check public DNS services every ' + '10 seconds for up to 20 minutes. If set to a non-zero value, a fixed DNS sleep time will be ' + 'used and the local DNS servers will be queried instead. A DNS sleep time of 120 seconds or ' + 'more is recommended for some DNS APIs.', + ), + dns_active24_token=dict(type='str', required=False, no_log=True), + dns_ad_key=dict(type='str', required=False, no_log=True), + dns_ali_key=dict(type='str', required=False), + dns_ali_secret=dict(type='str', required=False, no_log=True), + dns_autodns_user=dict(type='str', required=False), + dns_autodns_password=dict(type='str', required=False, no_log=True), + dns_autodns_context=dict(type='str', required=False), + dns_aws_id=dict(type='str', required=False), + dns_aws_secret=dict(type='str', required=False, no_log=True), + dns_azuredns_subscriptionid=dict(type='str', required=False), + dns_azuredns_tenantid=dict(type='str', required=False), + dns_azuredns_appid=dict(type='str', required=False), + dns_azuredns_clientsecret=dict(type='str', required=False, no_log=True), + dns_bunny_api_key=dict(type='str', required=False, no_log=True), + dns_cf_email=dict(type='str', required=False), + dns_cf_key=dict(type='str', required=False), + dns_cf_token=dict(type='str', required=False, no_log=True), + dns_cf_account_id=dict(type='str', required=False), + dns_cf_zone_id=dict(type='str', required=False), + dns_cloudns_auth_id=dict(type='str', required=False), + dns_cloudns_sub_auth_id=dict(type='str', required=False), + dns_cloudns_auth_password=dict(type='str', required=False, no_log=True), + dns_cx_key=dict(type='str', required=False), + dns_cx_secret=dict(type='str', required=False, no_log=True), + dns_cyon_user=dict(type='str', required=False), + dns_cyon_password=dict(type='str', required=False, no_log=True), + dns_da_key=dict(type='str', required=False, no_log=True), + dns_da_insecure=dict(type='bool', required=False, default=False), + dns_ddnss_token=dict(type='str', required=False, no_log=True), + dns_dgon_key=dict(type='str', required=False, no_log=True), + dns_dnsexit_auth_user=dict(type='str', required=False), + dns_dnsexit_auth_pass=dict(type='str', required=False, no_log=True), + dns_dnsexit_api=dict(type='str', required=False, no_log=True), + dns_dnshome_password=dict(type='str', required=False, no_log=True), + dns_dnshome_subdomain=dict(type='str', required=False), + dns_dnsimple_token=dict(type='str', required=False, no_log=True), + dns_dnsservices_user=dict(type='str', required=False), + dns_dnsservices_password=dict(type='str', required=False, no_log=True), + dns_doapi_token=dict(type='str', required=False, no_log=True), + dns_do_pid=dict(type='str', required=False), + dns_do_password=dict(type='str', required=False, no_log=True), + dns_domeneshop_token=dict(type='str', required=False), + dns_domeneshop_secret=dict(type='str', required=False, no_log=True), + dns_dp_id=dict(type='str', required=False), + dns_dp_key=dict(type='str', required=False, no_log=True), + dns_duckdns_token=dict(type='str', required=False, no_log=True), + dns_dyn_customer=dict(type='str', required=False), + dns_dyn_user=dict(type='str', required=False), + dns_dyn_password=dict(type='str', required=False, no_log=True), + dns_dynu_clientid=dict(type='str', required=False), + dns_dynu_secret=dict(type='str', required=False, no_log=True), + dns_freedns_user=dict(type='str', required=False), + dns_freedns_password=dict(type='str', required=False, no_log=True), + dns_fornex_api_key=dict(type='str', required=False, no_log=True), + dns_gandi_livedns_key=dict(type='str', required=False), + dns_gandi_livedns_token=dict(type='str', required=False, no_log=True), + dns_gcloud_key=dict(type='str', required=False, no_log=True), + dns_googledomains_access_token=dict(type='str', required=False, no_log=True), + dns_googledomains_zone=dict(type='str', required=False), + dns_gd_key=dict(type='str', required=False), + dns_gd_secret=dict(type='str', required=False, no_log=True), + dns_hostingde_server=dict(type='str', required=False), + dns_hostingde_apiKey=dict(type='str', required=False, no_log=True), + dns_he_user=dict(type='str', required=False), + dns_he_password=dict(type='str', required=False, no_log=True), + dns_infoblox_credentials=dict(type='str', required=False, no_log=True), + dns_infoblox_server=dict(type='str', required=False), + dns_inwx_user=dict(type='str', required=False), + dns_inwx_password=dict(type='str', required=False, no_log=True), + dns_inwx_shared_secret=dict(type='str', required=False, no_log=True), + dns_ionos_prefix=dict(type='str', required=False), + dns_ionos_secret=dict(type='str', required=False, no_log=True), + dns_ipv64_token=dict(type='str', required=False, no_log=True), + dns_ispconfig_user=dict(type='str', required=False), + dns_ispconfig_password=dict(type='str', required=False, no_log=True), + dns_ispconfig_api=dict(type='str', required=False), + dns_ispconfig_insecure=dict(type='bool', required=False, default=False), + dns_jd_id=dict(type='str', required=False), + dns_jd_region=dict(type='str', required=False), + dns_jd_secret=dict(type='str', required=False, no_log=True), + dns_joker_username=dict(type='str', required=False), + dns_joker_password=dict(type='str', required=False, no_log=True), + dns_kinghost_username=dict(type='str', required=False), + dns_kinghost_password=dict(type='str', required=False, no_log=True), + dns_knot_server=dict(type='str', required=False), + dns_knot_key=dict(type='str', required=False, no_log=True), + dns_limacity_apikey=dict(type='str', required=False, no_log=True), + dns_linode_v4_key=dict(type='str', required=False, no_log=True), + dns_loopia_api=dict(type='str', required=False), + dns_loopia_user=dict(type='str', required=False), + dns_loopia_password=dict(type='str', required=False, no_log=True), + dns_lua_email=dict(type='str', required=False), + dns_lua_key=dict(type='str', required=False, no_log=True), + dns_miab_user=dict(type='str', required=False), + dns_miab_password=dict(type='str', required=False, no_log=True), + dns_miab_server=dict(type='str', required=False), + dns_me_key=dict(type='str', required=False), + dns_me_secret=dict(type='str', required=False, no_log=True), + dns_mydnsjp_masterid=dict(type='str', required=False), + dns_mydnsjp_password=dict(type='str', required=False, no_log=True), + dns_mythic_beasts_key=dict(type='str', required=False), + dns_mythic_beasts_secret=dict(type='str', required=False, no_log=True), + dns_namecheap_user=dict(type='str', required=False), + dns_namecheap_api=dict(type='str', required=False), + dns_namecheap_sourceip=dict(type='str', required=False), + dns_namecom_user=dict(type='str', required=False), + dns_namecom_token=dict(type='str', required=False, no_log=True), + dns_namesilo_key=dict(type='str', required=False, no_log=True), + dns_nederhost_key=dict(type='str', required=False, no_log=True), + dns_netcup_cid=dict(type='str', required=False), + dns_netcup_key=dict(type='str', required=False), + dns_netcup_pw=dict(type='str', required=False, no_log=True), + dns_njalla_token=dict(type='str', required=False, no_log=True), + dns_nsone_key=dict(type='str', required=False, no_log=True), + dns_nsupdate_server=dict(type='str', required=False), + dns_nsupdate_zone=dict(type='str', required=False), + dns_nsupdate_key=dict(type='str', required=False, no_log=True), + dns_oci_cli_user=dict(type='str', required=False), + dns_oci_cli_tenancy=dict(type='str', required=False), + dns_oci_cli_region=dict(type='str', required=False), + dns_oci_cli_key=dict(type='str', required=False, no_log=True), + dns_online_key=dict(type='str', required=False, no_log=True), + dns_opnsense_host=dict(type='str', required=False, default='localhost'), + dns_opnsense_port=dict(type='int', required=False, default=443), + dns_opnsense_key=dict(type='str', required=False), + dns_opnsense_token=dict(type='str', required=False, no_log=True), + dns_opnsense_insecure=dict(type='bool', required=False, default=False), + dns_ovh_app_key=dict(type='str', required=False), + dns_ovh_app_secret=dict(type='str', required=False, no_log=True), + dns_ovh_consumer_key=dict(type='str', required=False), + dns_ovh_endpoint=dict(type='str', required=False), + dns_pleskxml_user=dict(type='str', required=False), + dns_pleskxml_pass=dict(type='str', required=False, no_log=True), + dns_pleskxml_uri=dict(type='str', required=False), + dns_pdns_url=dict(type='str', required=False), + dns_pdns_serverid=dict(type='str', required=False), + dns_pdns_token=dict(type='str', required=False, no_log=True), + dns_porkbun_key=dict(type='str', required=False), + dns_porkbun_secret=dict(type='str', required=False, no_log=True), + dns_sl_key=dict(type='str', required=False, no_log=True), + dns_selfhost_user=dict(type='str', required=False), + dns_selfhost_password=dict(type='str', required=False, no_log=True), + dns_selfhost_map=dict(type='str', required=False), + dns_servercow_username=dict(type='str', required=False), + dns_servercow_password=dict(type='str', required=False, no_log=True), + dns_simply_api_key=dict(type='str', required=False, no_log=True), + dns_simply_account_name=dict(type='str', required=False), + dns_transip_username=dict(type='str', required=False), + dns_transip_key=dict(type='str', required=False, no_log=True), + dns_udr_user=dict(type='str', required=False), + dns_udr_password=dict(type='str', required=False, no_log=True), + dns_uno_key=dict(type='str', required=False, no_log=True), + dns_uno_user=dict(type='str', required=False), + dns_vscale_key=dict(type='str', required=False, no_log=True), + dns_vultr_key=dict(type='str', required=False, no_log=True), + dns_yandex_token=dict(type='str', required=False, no_log=True), + dns_zilore_key=dict(type='str', required=False, no_log=True), + dns_zm_key=dict(type='str', required=False, no_log=True), + dns_gdnsdk_user=dict(type='str', required=False), + dns_gdnsdk_password=dict(type='str', required=False, no_log=True), + dns_acmedns_user=dict(type='str', required=False), + dns_acmedns_password=dict(type='str', required=False, no_log=True), + dns_acmedns_subdomain=dict(type='str', required=False), + dns_acmedns_updateurl=dict(type='str', required=False), + dns_acmedns_baseurl=dict(type='str', required=False), + dns_acmeproxy_endpoint=dict(type='str', required=False), + dns_acmeproxy_username=dict(type='str', required=False), + dns_acmeproxy_password=dict(type='str', required=False, no_log=True), + dns_variomedia_key=dict(type='str', required=False, no_log=True), + dns_schlundtech_user=dict(type='str', required=False), + dns_schlundtech_password=dict(type='str', required=False, no_log=True), + dns_easydns_apitoken=dict(type='str', required=False), + dns_easydns_apikey=dict(type='str', required=False, no_log=True), + dns_euserv_user=dict(type='str', required=False), + dns_euserv_password=dict(type='str', required=False, no_log=True), + dns_leaseweb_key=dict(type='str', required=False, no_log=True), + dns_cn_user=dict(type='str', required=False), + dns_cn_password=dict(type='str', required=False, no_log=True), + dns_arvan_token=dict(type='str', required=False, no_log=True), + dns_artfiles_username=dict(type='str', required=False), + dns_artfiles_password=dict(type='str', required=False, no_log=True), + dns_hetzner_token=dict(type='str', required=False, no_log=True), + dns_hexonet_login=dict(type='str', required=False), + dns_hexonet_password=dict(type='str', required=False, no_log=True), + dns_1984hosting_user=dict(type='str', required=False), + dns_1984hosting_password=dict(type='str', required=False, no_log=True), + dns_kas_login=dict(type='str', required=False), + dns_kas_authdata=dict(type='str', required=False, no_log=True), + dns_kas_authtype=dict(type='str', required=False, default='plain', choices=['plain', 'sha1']), + dns_desec_token=dict(type='str', required=False, no_log=True), + dns_desec_name=dict(type='str', required=False), + dns_infomaniak_token=dict(type='str', required=False, no_log=True), + dns_zone_username=dict(type='str', required=False), + dns_zone_key=dict(type='str', required=False, no_log=True), + dns_dynv6_token=dict(type='str', required=False, no_log=True), + dns_cpanel_user=dict(type='str', required=False), + dns_cpanel_token=dict(type='str', required=False, no_log=True), + dns_cpanel_hostname=dict(type='str', required=False), + dns_regru_username=dict(type='str', required=False), + dns_regru_password=dict(type='str', required=False, no_log=True), + dns_nic_username=dict(type='str', required=False), + dns_nic_password=dict(type='str', required=False, no_log=True), + dns_nic_client=dict(type='str', required=False), + dns_nic_secret=dict(type='str', required=False, no_log=True), + dns_world4you_username=dict(type='str', required=False), + dns_world4you_password=dict(type='str', required=False, no_log=True), + dns_aurora_key=dict(type='str', required=False), + dns_aurora_secret=dict(type='str', required=False, no_log=True), + dns_conoha_user=dict(type='str', required=False), + dns_conoha_password=dict(type='str', required=False, no_log=True), + dns_conoha_tenantid=dict(type='str', required=False), + dns_conoha_idapi=dict(type='str', required=False), + dns_constellix_key=dict(type='str', required=False), + dns_constellix_secret=dict(type='str', required=False, no_log=True), + dns_exoscale_key=dict(type='str', required=False), + dns_exoscale_secret=dict(type='str', required=False, no_log=True), + dns_internetbs_key=dict(type='str', required=False), + dns_internetbs_password=dict(type='str', required=False, no_log=True), + dns_pointhq_key=dict(type='str', required=False, no_log=True), + dns_pointhq_email=dict(type='str', required=False), + dns_rackspace_user=dict(type='str', required=False), + dns_rackspace_key=dict(type='str', required=False, no_log=True), + dns_rage4_token=dict(type='str', required=False, no_log=True), + dns_rage4_user=dict(type='str', required=False), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Validation(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias.py new file mode 100644 index 0000000..7c4c269 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/firewall.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import \ + module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + RELOAD_MOD_ARG_DEF_FALSE, OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.alias import \ + ALIAS_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.alias import Alias + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/alias.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/alias.html' + + +def run_module(): + module_args = dict( + **ALIAS_MOD_ARGS, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG_DEF_FALSE, # default-true takes pretty long sometimes (urltables and so on) + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + mutually_exclusive=[ + ('name', 'multi'), ('name', 'multi_purge'), ('name', 'multi_control.purge_all') + ], + required_one_of=[ + ('name', 'multi', 'multi_purge', 'multi_control.purge_all'), + ], + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module_wrapper(Alias(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias_multi.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias_multi.py new file mode 100644 index 0000000..5adbfdc --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias_multi.py @@ -0,0 +1,95 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/firewall.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import \ + module_multi_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.multi import \ + build_multi_mod_args, MultiModuleCallbacks + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + RELOAD_MOD_ARG_DEF_FALSE, OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.alias import \ + ALIAS_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.alias import Alias + from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import ensure_list + from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.alias import \ + builtin_alias, build_updatefreq + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/alias.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/alias.html' + + +class MultiCallbacks(MultiModuleCallbacks): + @staticmethod + def build(entry: dict) -> dict: + entry['content'] = list(map(str, ensure_list(entry['content']))) + if 'updatefreq_days' in entry: + entry['updatefreq_days'] = build_updatefreq(entry['updatefreq_days']) + + return entry + + @staticmethod + def purge_exclude(entry: dict) -> bool: + return builtin_alias(entry['name']) + + +def run_module(): + entry_multi_args = build_multi_mod_args( + mod_args=ALIAS_MOD_ARGS, + aliases=['aliases'], + not_required=['name'], + ) + + module_args = dict( + **entry_multi_args, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG_DEF_FALSE, # default-true takes pretty long sometimes (urltables and so on) + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_one_of=[ + ('multi', 'multi_purge', 'multi_control.purge_all'), + ], + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module_multi_wrapper( + module=module, + result=result, + obj=Alias, + kind='alias', + entry_args=entry_multi_args, + callbacks=MultiCallbacks(), + ) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias_purge.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias_purge.py new file mode 100644 index 0000000..c7c0c45 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/alias_purge.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/firewall.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, PURGE_MOD_ARGS, INFO_MOD_ARG, RELOAD_MOD_ARG + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/alias.html' +# EXAMPLES = 'https://github.com/O-X-L/ansible-opnsense/blob/latest/docs/tests/alias.yml' + + +def run_module(): + module_args = dict( + aliases=dict( + type='dict', required=False, default={}, + description='Configured aliases - compared against existing ones' + ), + fail_all=dict( + type='bool', required=False, default=False, aliases=['fail'], + description='Fail module if single alias fails to be purged.' + ), + **RELOAD_MOD_ARG, + **INFO_MOD_ARG, + **PURGE_MOD_ARGS, + **OPN_MOD_ARGS, + ) + + AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ).fail_json('This module was deprecated in favor of: https://ansible-opnsense.oxl.app/modules/1_multi.html') + + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_acl.py new file mode 100644 index 0000000..c0da934 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_acl.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/bind.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_acl import Acl + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/bind.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/bind.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True, aliases=['n']), + networks=dict( + type='list', elements='str', required=False, aliases=['nets'], + ), + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Acl(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_blocklist.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_blocklist.py new file mode 100644 index 0000000..201f71a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_blocklist.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/bind.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_blocklist import Blocklist + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/bind.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/bind.html' + +BL_MAPPING = { + 'AdAway List': 'aa', + 'AdGuard List': 'ag', + 'Blocklist.site Ads': 'bla', + 'Blocklist.site Fraud': 'blf', + 'Blocklist.site Phishing': 'blp', + 'Cameleon List': 'ca', + 'Easy List': 'el', + 'EMD Malicious Domains List': 'emd', + 'Easyprivacy List': 'ep', + 'hpHosts Ads': 'hpa', + 'hpHosts FSA': 'hpf', + 'hpHosts PSH': 'hpp', + 'hpHosts PUP': 'hup', + 'Malwaredomain List': 'mw', + 'NoCoin List': 'nc', + 'PornTop1M List': 'pt', + 'Ransomware Tracker List': 'rw', + 'Simple Ad List': 'sa', + 'Simple Tracker List': 'st', + 'Steven Black List': 'sb', + 'WindowsSpyBlocker (spy)': 'ws', + 'WindowsSpyBlocker (update)': 'wsu', + 'WindowsSpyBlocker (extra)': 'wse', + 'YoYo List': 'yy', +} + + +def run_module(): + module_args = dict( + block=dict( + type='list', elements='str', required=False, choices=list(BL_MAPPING.keys()), + aliases=['lists'], default=[], + description="Blocklist's you want to enable" + ), + exclude=dict( + type='list', elements='str', required=False, default=[], + aliases=['safe_list'], + description='Domains to exclude from the filter' + ), + safe_google=dict( + type='bool', required=False, default=False, aliases=['safe_search_google'], + ), + safe_duckduckgo=dict( + type='bool', required=False, default=False, aliases=['safe_search_duckduckgo'], + ), + safe_youtube=dict( + type='bool', required=False, default=False, aliases=['safe_search_youtube'], + ), + safe_bing=dict( + type='bool', required=False, default=False, aliases=['safe_search_bing'], + ), + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + translated_lists = [] + for k in module.params['block']: + translated_lists.append(BL_MAPPING[k]) + + module.params['block'] = translated_lists + + module_wrapper(Blocklist(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_domain.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_domain.py new file mode 100644 index 0000000..eb46956 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_domain.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/bind.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_domain import \ + Domain + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/bind.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/bind.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True, aliases=['domain_name', 'domain']), + mode=dict( + type='str', required=False, default='primary', choices=['primary', 'secondary'] + ), + primary=dict( + type='list', elements='str', required=False, aliases=['primary_ip', 'master', 'master_ip'], default=[], + description='Set the IP address of primary server when using secondary mode' + ), + transfer_key_algo=dict( + type='str', required=False, + choices=[ + 'hmac-sha512', 'hmac-sha384', 'hmac-sha256', 'hmac-sha224', + 'hmac-sha1', 'hmac-md5', + ] + ), + transfer_key_name=dict(type='str', required=False), + transfer_key=dict(type='str', required=False, no_log=True), + allow_notify=dict( + type='list', elements='str', required=False, default=[], + aliases=['allow_notify_secondary', 'allow_notify_slave'], + description='A list of allowed IP addresses to receive notifies from' + ), + transfer_acl=dict( + type='list', elements='str', required=False, default=[], aliases=['allow_transfer'], + description='An ACL where you allow which server can retrieve this zone' + ), + query_acl=dict( + type='list', elements='str', required=False, default=[], aliases=['allow_query'], + description='An ACL where you allow which client are allowed ' + 'to query this zone' + ), + ttl=dict( + type='int', required=False, default=86400, + description='The general Time To Live for this zone' + ), + refresh=dict( + type='int', required=False, default=21600, + description='The time in seconds after which name servers should ' + 'refresh the zone information' + ), + retry=dict( + type='int', required=False, default=3600, + description='The time in seconds after which name servers should ' + 'retry requests if the primary does not respond' + ), + expire=dict( + type='int', required=False, default=3542400, + description='The time in seconds after which name servers should ' + 'stop answering requests if the primary does not respond' + ), + negative=dict( + type='int', required=False, default=3600, + description='The time in seconds after which an entry for a ' + 'non-existent record should expire from cache' + ), + admin_mail=dict( + type='str', required=False, default='mail.opnsense.localdomain', + description='The mail address of zone admin. A @-sign will ' + 'automatically be replaced with a dot in the zone data' + ), + server=dict( + type='str', required=False, default='opnsense.localdomain', aliases=['dns_server'], + description='Set the DNS server hosting this file. This should usually ' + 'be the FQDN of your firewall where the BIND plugin is installed' + ), + # serial=dict(type='str', required=False), + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Domain(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_general.py new file mode 100644 index 0000000..3cd9fa6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_general.py @@ -0,0 +1,172 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/bind.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/bind.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/bind.html' + + +def run_module(): + module_args = dict( + ipv6=dict( + type='bool', required=False, default=True, aliases=['v6'], + description='If IPv6 should be enabled' + ), + response_policy_zones=dict( + type='bool', required=False, default=True, + aliases=['rpz'], + description='If response-policy-zones should be enabled' + ), + port=dict( + type='int', required=False, aliases=['p'], default=53530, + description='Port the service should listen on' + ), + listen_ipv4=dict( + type='list', elements='str', required=False, default=['127.0.0.1'], + aliases=['listen_v4', 'listen'], + description='IPv4 addresses the service should listen on' + ), + listen_ipv6=dict( + type='list', elements='str', required=False, default=['::1'], + aliases=['listen_v6'], + description='IPv6 addresses the service should listen on' + ), + query_source_ipv4=dict( + type='str', required=False, + aliases=['query_ipv4', 'query_v4'], + description='Specify the IPv4 address used as a source for outbound queries' + ), + query_source_ipv6=dict( + type='str', required=False, + aliases=['query_ipv6', 'query_v6'], + description='Specify the IPv6 address used as a source for outbound queries' + ), + transfer_source_ipv4=dict( + type='str', required=False, + aliases=['transfer_ipv4', 'transfer_v4'], + description='Specify the IPv4 address used as a source for zone transfers' + ), + transfer_source_ipv6=dict( + type='str', required=False, + aliases=['transfer_ipv6', 'transfer_v6'], + description='Specify the IPv6 address used as a source for zone transfers' + ), + forwarders=dict( + type='list', elements='str', required=False, default=[], + aliases=['fwd'], + description='Set one or more hosts to send your DNS queries if the request is unknown' + ), + filter_aaaa_v4=dict( + type='bool', required=False, default=False, + description='This will filter AAAA records on IPv4 Clients' + ), + filter_aaaa_v6=dict( + type='bool', required=False, default=False, + description='This will filter AAAA records on IPv6 Clients' + ), + filter_aaaa_acl=dict( + type='list', elements='str', required=False, default=[], + description='Specifies a list of client addresses for which AAAA filtering is to be applied' + ), + log_size=dict( + type='int', required=False, default=5, aliases=['max_log_size'], + description='Maximum log file size in MB' + ), + cache_size=dict( + type='int', required=False, default=50, + aliases=['max_cache_size', 'cache_percentage', 'max_cache_percentage'], + description='How much memory in percent the cache can use from the system' + ), + recursion_acl=dict( + type='list', elements='str', required=False, default=[], + aliases=['recursion'], + description='Define an ACL where you allow which clients can resolve via ' + 'this service. Usually use your local LAN' + ), + transfer_acl=dict( + type='list', elements='str', required=False, default=[], + aliases=['allow_transfer', 'transfer'], + description='Define the ACLs where you allow which server can retrieve zones' + ), + query_acl=dict( + type='list', elements='str', required=False, default=[], + aliases=['allow_query', 'query'], + description='Define the ACLs where you allow which client are allowed to query this server' + ), + dnssec_validation=dict( + type='str', required=False, default='no', + aliases=['dnssec'], choices=['no', 'auto'], + description='Set to "Auto" to use the static trust anchor configuration by the system' + ), + hide_hostname=dict( + type='bool', required=False, default=False, + description='This will hide the system hostname for DNS queries' + ), + hide_version=dict( + type='bool', required=False, default=True, + description='This will hide the local BIND version in DNS queries' + ), + prefetch=dict( + type='bool', required=False, default=True, + description='If prefetching of domains should be enabled' + ), + ratelimit=dict( + type='bool', required=False, default=False, + description='This will enable rate-limiting for DNS replies' + ), + ratelimit_count=dict( + type='str', required=False, + description='Set how many replies per second are allowed' + ), + ratelimit_except=dict( + type='list', elements='str', required=False, + default=['127.0.0.1', '::1'], + description='Except a list of IPs from rate-limiting' + ), + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_record.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_record.py new file mode 100644 index 0000000..c17967e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_record.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/bind.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import \ + module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.bind import \ + BIND_REC_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_record import \ + Record + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/bind.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/bind.html' + + +def run_module(): + module_args = dict( + **BIND_REC_MOD_ARGS, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module_wrapper(Record(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_record_multi.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_record_multi.py new file mode 100644 index 0000000..eebc77d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/bind_record_multi.py @@ -0,0 +1,95 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/bind.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import \ + module_multi_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.multi import \ + build_multi_mod_args, MultiModuleCallbacks + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.bind import \ + BIND_REC_MOD_ARGS, BIND_REC_MATCH_FIELDS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_record import \ + Record + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/bind.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/bind.html' + + +class MultiCallbacks(MultiModuleCallbacks): + @staticmethod + def get_existing(meta_entry: Record) -> dict: + meta_entry.search_call_domains() + return { + 'main': meta_entry.get_existing(), + 'domains': meta_entry.existing_domains, + } + + @staticmethod + def set_existing(entry: Record, cache: dict): + entry.existing_entries = cache['main'] + entry.existing_domains = cache['domains'] + + +def run_module(): + entry_multi_args = build_multi_mod_args( + mod_args=BIND_REC_MOD_ARGS, + aliases=['records'], + not_required=['domain'], # overrides could be used to define the domain + ) + + module_args = dict( + **entry_multi_args, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + **BIND_REC_MATCH_FIELDS, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_one_of=[ + ('multi', 'multi_purge', 'multi_control.purge_all'), + ], + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module_multi_wrapper( + module=module, + result=result, + obj=Record, + kind='record', + entry_args=entry_multi_args, + callbacks=MultiCallbacks(), + ) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/cron.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/cron.py new file mode 100644 index 0000000..e003b69 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/cron.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/cron.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.cron import CronJob + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/cron.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/cron.html' + + +def run_module(): + module_args = dict( + description=dict(type='str', required=True, aliases=['desc']), + minutes=dict( + type='str', required=False, default='0', aliases=['min', 'm'], + description='Value needs to be between 0 and 59; multiple values, ranges, ' + 'steps and asterisk are supported (ex. 1,10,20,30 or 1-30).' + ), + hours=dict( + type='str', required=False, default='0', aliases=['hour', 'h'], + description='Value needs to be between 0 and 23; multiple values, ranges, ' + 'steps and asterisk are supported (ex. 1,2,8 or 0-8).' + ), + days=dict( + type='str', required=False, default='*', aliases=['day', 'd'], + description='Value needs to be between 1 and 31; multiple values, ranges, L ' + '(last day of month), steps and asterisk are supported (ex. 1,2,8 or 1-28).' + ), + months=dict( + type='str', required=False, default='*', aliases=['month', 'M'], + description='Value needs to be between 1 and 12 or JAN to DEC, multiple values, ' + 'ranges, steps and asterisk are supported (ex. JAN,2,10 or 3-8).', + ), + weekdays=dict( + type='str', required=False, default='*', aliases=['wd'], + description='Value needs to be between 0 and 7 (Sunday to Sunday), multiple values, ' + 'ranges, steps and asterisk are supported (ex. 1,2,4 or 0-4).' + ), + who=dict(type='str', required=False, default='root', description='User who should run the command'), + command=dict( + type='str', required=False, aliases=['cmd'], + description="One of the pre-defined commands seen in the WEB-GUI. Per example: " + "'automatic firmware update', 'system remote backup' or 'ipsec restart' " + "(always all-lowercase)" + ), + parameters=dict( + type='str', required=False, aliases=['params'], + description='Enter parameters for this job if required' + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(CronJob(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_controlagent.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_controlagent.py new file mode 100644 index 0000000..af83aba --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_controlagent.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/wireguard.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcp_controlagent import ControlAgent + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dhcp.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dhcp.html' + + +def run_module(): + module_args = dict( + http_port=dict( + type='int', required=False, default=8000, + description='Portnumber to use for the RESTful interface' + ), + http_host=dict( + type='str', required=False, default='127.0.0.1', aliases=['host'], + description='Address on which the RESTful interface should be available' + ), + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(ControlAgent(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_general.py new file mode 100644 index 0000000..fed4989 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_general.py @@ -0,0 +1,74 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/nginx.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + EN_ONLY_MOD_ARG, OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcp_general import General + + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dhcp.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dhcp.html' + + +def run_module(): + module_args = dict( + interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['ints'], + description='Comma separated list of network interfaces to listen on for DHCP requests' + ), + socket_type=dict( + type='str', required=False, default='raw', choices=['raw', 'udp'], aliases=['dhcp_socket_type'], + description='Socket type used for DHCP communication', + ), + fw_rules=dict( + type='bool', required=False, default=True, aliases=['fwrules', 'rules'], + description='Automatically add a basic set of firewall rules to allow dhcp traffic, ' + 'more fine grained controls can be offered manually when disabling this option', + ), + lifetime=dict( + type='int', required=False, default=4000, aliases=['valid_lifetime'], + description='Defines how long the addresses (leases) given out by the server are valid (in seconds)', + ), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_reservation.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_reservation.py new file mode 100644 index 0000000..8521695 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_reservation.py @@ -0,0 +1,78 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/kea.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcp_reservation_v4 import ReservationV4 + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dhcp.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dhcp.html' + + +def run_module(): + module_args = dict( + ip=dict( + type='str', required=True, aliases=['ip_address'], + description='IP address to offer to the client', + ), + mac=dict( + type='str', required=False, aliases=['mac_address'], + description='MAC/Ether address of the client in question', + ), + subnet=dict( + type='str', required=False, + description='Subnet this reservation belongs to', + ), + hostname=dict( + type='str', required=False, + description='Offer a hostname to the client', + ), + description=dict(type='str', required=False, aliases=['desc']), + ipv=dict(type='int', required=False, default=4, choices=[4, 6], aliases=['ip_version']), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + if module.params['ipv'] == 6: + module.fail_json('DHCPv6 is not yet supported!') + + module_wrapper(ReservationV4(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_subnet.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_subnet.py new file mode 100644 index 0000000..8f8c92d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcp_subnet.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/kea.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcp_subnet_v4 import SubnetV4 + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dhcp.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dhcp.html' + + +def run_module(): + module_args = dict( + subnet=dict( + type='str', required=True, + description='Subnet to use, should be large enough to hold the specified pools and reservations', + ), + description=dict( + type='str', required=False, aliases=['desc'], default='', + ), + pools=dict( + type='list', elements='str', required=False, default=[], + description='List of pools, one per line in range or subnet format ' + '(e.g. 192.168.0.100 - 192.168.0.200 , 192.0.2.64/26)' + ), + auto_options=dict( + type='bool', required=False, default=True, aliases=['option_data_autocollect'], + description='Automatically update option data for relevant attributes as routers, ' + 'dns servers and ntp servers when applying settings from the gui.' + ), + gateway=dict( + type='list', elements='str', required=False, aliases=['gw', 'routers'], default=[], + description='Default gateways to offer to the clients', + ), + routes=dict( + type='str', required=False, aliases=['static_routes'], default='', + description='Static routes that the client should install in its routing cache, ' + 'defined as dest-ip1,router-ip1;dest-ip2,router-ip2', + ), + dns=dict( + type='list', elements='str', required=False, aliases=['dns_servers', 'dns_srv'], default=[], + description='DNS servers to offer to the clients', + ), + domain=dict( + type='str', required=False, aliases=['domain_name', 'dom_name', 'dom'], default='', + description="The domain name to offer to the client, set to this firewall's domain name when left empty", + ), + domain_search=dict( + type='list', elements='str', required=False, aliases=['dom_search'], default=[], + description="Specifies a ´search list´ of Domain Names to be used by the client to locate " + 'not-fully-qualified domain names.', + ), + ntp_servers=dict( + type='list', elements='str', required=False, aliases=['ntp_srv', 'ntp'], default=[], + description='Specifies a list of IP addresses indicating NTP (RFC 5905) servers available to the client.', + ), + time_servers=dict( + type='list', elements='str', required=False, aliases=['time_srv'], default=[], + description='Specifies a list of RFC 868 time servers available to the client.', + ), + next_server=dict( + type='str', required=False, aliases=['next_srv'], default='', + description='Next server IP address', + ), + tftp_server=dict( + type='str', required=False, aliases=['tftp', 'tftp_srv', 'tftp_server_name'], default='', + description='TFTP server address or fqdn', + ), + tftp_file=dict( + type='str', required=False, aliases=['tftp_boot_file', 'boot_file_name'], default='', + description='TFTP Boot filename to request', + ), + ipv=dict(type='int', required=False, default=4, choices=[4, 6], aliases=['ip_version']), + v6_only_preferred=dict( + type='int', required=False, aliases=['v6_preferred'], + description='The number of seconds for which the client should disable DHCPv4. ' + 'The minimum value is 300 seconds.' + ), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured interface with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['subnet', 'description'], + default=['subnet'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + if module.params['ipv'] == 6: + module.fail_json('DHCPv6 is not yet supported!') + + module_wrapper(SubnetV4(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcrelay_destination.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcrelay_destination.py new file mode 100644 index 0000000..89fbeea --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcrelay_destination.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dhcrelay.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcrelay_destination import \ + DhcRelayDestination + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dhcrelay_destination.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dhcrelay_destination.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='A unique name for this relay destination.', + ), + server=dict( + type='list', elements='str', required=False, + description='A list of server IP addresses to relay DHCP requests to.' + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(DhcRelayDestination(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcrelay_relay.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcrelay_relay.py new file mode 100644 index 0000000..40e7549 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dhcrelay_relay.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dhcrelay.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcrelay_relay import DhcRelayRelay + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dhcrelay_relay.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dhcrelay_relay.html' + + +def run_module(): + module_args = dict( + enabled=dict( + type='bool', default=False, + description='Enable or disable this relay.', + ), + interface=dict( + type='str', required=True, aliases=['i', 'int'], + description='The interface to relay DHCP requests from. ' + ), + destination=dict( + type='str', required=False, aliases=['dest'], + description='The uuid of the destination server group to relay DHCP requests to.' + ), + agent_info=dict( + type='bool', default=False, + description='Add the relay agent information option.', + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(DhcRelayRelay(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_boot.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_boot.py new file mode 100644 index 0000000..8934a07 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_boot.py @@ -0,0 +1,84 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dnsmasq.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_boot import Boot + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc'], + description='DHCP boot description.', + ), + interface=dict( + type='str', required=False, aliases=['int'], + description='Interface this boot options is set for.', + ), + tag=dict( + type='list', elements='str', required=False, default=[], aliases=['t'], + description='DHCP boot option is only sent when all the tags are matched.', + ), + filename=dict( + type='str', required=False, aliases=['file', 'f'], + description='DHCP boot file path.', + ), + servername=dict( + type='str', required=False, + description='DHCP boot server name.', + ), + address=dict( + type='str', required=False, + description='DHCP boot server address.', + ), + **STATE_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('filename',)), + ] + ) + + module_wrapper(Boot(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_domain.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_domain.py new file mode 100644 index 0000000..c67b89b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_domain.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dnsmasq.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_domain import Domain + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' + + +def run_module(): + module_args = dict( + domain=dict( + type='str', required=True, aliases=['name'], + description='Domain to override.', + ), + sequence=dict( + type='int', required=False, default=1, aliases=['seq'], + description='Sort with a sequence number.' + ), + ip=dict( + type='str', required=False, + description='Source IP address for queries to the DNS server for the override domain.', + ), + port=dict( + type='int', required=False, + description='Specify a non standard port number.', + ), + src_ip=dict( + type='str', required=False, + description='Source IP address for queries to the DNS server for the override domain.', + ), + ipset=dict( + type='str', required=False, + description='When a client resolves the domain, the resolved IP addresses will be added to the alias.', + ), + description=dict( + type='str', required=False, + description='Description here for your reference.', + ), + **STATE_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Domain(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_general.py new file mode 100644 index 0000000..f43be5e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_general.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dnsmasq.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + EN_ONLY_MOD_ARG, OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' + + +def run_module(): + module_args = dict( + # Default + interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['ints'], + description='Interface IPs used by Dnsmasq for responding to queries from clients. ' + 'If an interface has both IPv4 and IPv6 IPs, both are used. ' + 'Queries to other interface IPs not selected below are discarded. ' + 'The default behavior is to respond to queries on every available IPv4 and IPv6 address.', + ), + strictbind=dict( + type='bool', required=False, default=False, + description='By default we bind the wildcard address, even when listening on some interfaces. ' + 'Requests that shouldnt be handled are discarded, this has the advantage of working ' + 'even when interfaces come and go and change address. ' + 'This option forces binding to only the interfaces we are listening on, ' + 'which is less stable in non static environments.' + ), + # DNS + port=dict( + type='int', required=False, default=53, aliases=['dns_port'], + description='The port used for responding to DNS queries. ' + 'It should normally be left blank unless' + 'another service needs to bind to TCP/UDP port 53. ' + 'Setting this to zero (0) completely disables DNS function' + ), + dnssec=dict( + type='bool', default='False', + description='Secure DNS' + ), + resolve_etc_hosts=dict( + type='bool', default='True', aliases=['resolve_hosts'], + description='Do read hostnames in /etc/hosts' + ), + log_queries=dict( + type='bool', default='False', + description='If this option is set, we will log the DNS query' + ), + dns_forward_max=dict( + type='int', required=False, + description='Maximum number of concurrent DNS queries', + ), + cache_size=dict( + type='int', required=False, + description='Size of the DNS cache. Setting the cache size to zero disables caching', + ), + local_ttl=dict( + type='int', required=False, + description='Time-to-live (in seconds) to be given for local DNS entries, i.e. /etc/hosts or DHCP leases', + ), + ident=dict( + type='bool', default=False, + description='Do respond to class CHAOS and type TXT in domain bind queries. ' + 'Without this option being set, the cache statistics are also available in the DNS ' + 'as answers to queries of class CHAOS and type TXT in domain bind.' + ), + # DNS Query Forwarding + strict_order=dict( + type='bool', default='False', + description='If this option is set, we will query the DNS servers sequentially ' + 'in the order specified (System: General Setup: DNS Servers),' + 'rather than all at once in parallel.' + ), + domain_needed=dict( + type='bool', default='False', + description='If this option is set, we will not forward A or AAAA queries for plain names, ' + 'without dots or domain parts, to upstream name servers. ' + 'If the name is not known from /etc/hosts or DHCP then a "not found" answer is returned.' + ), + resolv_system=dict( + type='bool', required=False, default=True, + description='Forward DNS queries to system nameservers', + ), + forward_private_reverse=dict( + type='bool', default='True', + description='If this option is set, we will forward reverse DNS lookups (PTR) for ' + 'private addresses (RFC 1918) to upstream name servers. ' + 'Any entries in the Domain Overrides section forwarding private "n.n.n.in-addr.arpa" ' + 'names to a specific server are still forwarded if disabled. ' + 'If the IP to name is not known from /etc/hosts, DHCP or a specific domain override then ' + 'a "not found" answer is immediately returned in this case.' + ), + add_mac=dict( + type='str', required=False, options=['', 'standard', 'base64', 'text'], default='', + description='Add the MAC address of the requestor to DNS queries which are forwarded upstream.', + ), + add_subnet=dict( + type='bool', required=False, default=False, + description='Add the real client addresses to DNS queries which are forwarded upstream.', + ), + strip_subnet=dict( + type='bool', required=False, default=False, + description='Strip the subnet received by a downstream DNS server.', + ), + # DHCP + dhcp_disable_interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['dhcp_dis_ints', 'ints_no_dhcp'], + description='Do not provide DHCP, TFTP or router advertisement on the specified interfaces.', + ), + dhcp_fqdn=dict( + type='bool', required=False, default=False, + description='Registers the qualified names of DHCP clients into the DNS.', + ), + dhcp_domain=dict( + type='str', required=False, + description='Domain part to registers the qualified names of DHCP clients into the DNS.', + ), + dhcp_local=dict( + type='bool', required=False, default=True, + description='Sets all DHCP domains as local.', + ), + dhcp_lease_max=dict( + type='int', required=False, + description='Limits Dnsmasq to the specified maximum number of DHCP leases.', + ), + dhcp_authoritative=dict( + type='bool', required=False, default=False, + description='Should be set when Dnsmasq is definitely the only DHCP server on a network.', + ), + dhcp_reply_delay=dict( + type='int', required=False, + description='Delays sending DHCPOFFER and PROXYDHCP replies for at least the specified number of seconds', + ), + dhcp_default_fw_rules=dict( + type='bool', required=False, default=True, + description='Automatically register firewall rules to allow DHCP traffic for all selected interfaces.', + ), + dhcp_enable_ra=dict( + type='bool', required=False, default=False, + description='Enable Router Advertisements for all configured DHCPv6 ranges.', + ), + dhcp_hasync=dict( + type='bool', required=False, default=True, + description='HA sync DHCP general settings.', + ), + # ISC / KEA DHCP (legacy) + regdhcp=dict( + type='bool', default=False, required=False, + description='If this option is set, then machines that specify their hostname when requesting a ' + 'DHCP lease will be registered, so that their name can be resolved.' + ), + regdhcpdomain=dict( + type='str', default='', + description='The domain name to use for DHCP hostname registration. ' + 'If empty, the default system domain is used. ' + 'Note that all DHCP leases will be assigned to the same domain. ' + 'If this is undesired, static DHCP lease registration is able to provide coherent mappings.' + ), + regdhcpstatic=dict( + type='bool', default='False', required=False, + description='If this option is set, then DHCP static mappings will be registered, ' + 'so that their name can be resolved.' + ), + dhcpfirst=dict( + type='bool', default='False', + description='If this option is set, then DHCP mappings will be resolved before ' + 'the manual list of names below.' + 'This only affects the name given for a reverse lookup (PTR).' + ), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_host.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_host.py new file mode 100644 index 0000000..ca4ba98 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_host.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dnsmasq.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_host import Host + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc'], + description='DHCP host description.', + ), + host=dict( + type='str', required=False, aliases=['h'], + description='Name of the host, without the domain part.', + ), + domain=dict( + type='str', required=False, aliases=['d'], + description='Domain of the host.', + ), + local=dict( + type='bool', required=False, default=False, + description='Set the domain as local.', + ), + ip=dict( + type='list', elements='str', required=False, default=[], + description='IP addresses of the host.', + ), + aliases=dict( + type='list', elements='str', required=False, default=[], + description='Adds additional static A, AAAA and PTR records for the given alternative names (FQDN).', + ), + cnames=dict( + type='list', elements='str', required=False, default=[], + description='Adds additional CNAME records for the given alternative names.', + ), + # DHCP + client_id=dict( + type='str', required=False, + description='DHCP boot server address.', + ), + hardware_addr=dict( + type='list', elements='str', required=False, aliases=['mac'], default=[], + description='Hardware address of the client.', + ), + lease_time=dict( + type='int', required=False, + description='Time the lease is valid.', + ), + ignore=dict( + type='bool', required=False, default=False, + description='Ignore any DHCP packets of this host.', + ), + set_tag=dict( + type='str', required=False, + description='Tag to set for matching requests.', + ), + comments=dict( + type='str', required=False, + description='A comment for your reference (not parsed).', + ), + **STATE_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Host(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_option.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_option.py new file mode 100644 index 0000000..3c10f9f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_option.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dnsmasq.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_option import Option + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc'], + description='DHCP option description.', + ), + type=dict( + type='str', required=False, options=['set', 'match'], default='set', + description='"Set" to send it to a client in a DHCP offer or ' + '"Match" to dynamically tag clients that send it in the initial DHCP request.', + ), + option=dict( + type='int', required=False, + description='DHCPv4 option to offer to the client.', + ), + option6=dict( + type='int', required=False, + description='DHCPv6 option to offer to the client.', + ), + interface=dict( + type='str', required=False, aliases=['int'], + description='Interface this options is set for.', + ), + tag=dict( + type='list', elements='str', required=False, default=[], aliases=['t'], + description='DHCP option is only sent when all the tags do match.', + ), + set_tag=dict( + type='str', required=False, + description='Tag to set for matching requests.', + ), + value=dict( + type='str', required=False, + description='Value (or values) to send to the client. ' + 'When using "Match", leave empty to match on the option only.', + ), + force=dict( + type='bool', required=False, default=False, + description='Always send the option, also when the client does not ask for it.', + ), + **STATE_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('option', 'option6'), True), + ] + ) + + module_wrapper(Option(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_range.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_range.py new file mode 100644 index 0000000..6bdefe2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_range.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dnsmasq.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_range import Range + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc'], + description='DHCP range description.', + ), + interface=dict( + type='str', required=False, aliases=['int'], + description='Interface to serve this range.', + ), + set_tag=dict( + type='str', required=False, + description='Tag to set for matching requests.', + ), + start_addr=dict( + type='str', required=False, + description='Start of the range, e.g. 192.168.1.100 for DHCPv4, 2000::1 for DHCPv6.', + ), + end_addr=dict( + type='str', required=False, + description='End of the range.', + ), + subnet_mask=dict( + type='str', required=False, + description='Subnet mask of the range. Leave empty to auto-calculate the subnet mask.', + ), + constructor=dict( + type='str', required=False, + description='Interface to use to calculate a DHCPv6 or RA range.', + ), + mode=dict( + type='list', elements='str', required=False, + description='Mode flags to set for this range, "static" means no addresses will be automatically assigned.', + ), + prefix_len=dict( + type='int', required=False, default=64, + description='Prefix length offered to the client.', + ), + lease_time=dict( + type='int', required=False, default=86400, + description='Defines how long the addresses (leases) given out by the server are valid.', + ), + domain_type=dict( + type='str', required=False, options=['interface', 'range'], default='range', + description='If only clients in this range, or all clients in any subnets on the selected interface match.', + ), + domain=dict( + type='str', required=False, + description='Offer this domain to DHCP clients.', + ), + sync=dict( + type='bool', required=False, default=True, + description='Ignore this range from being transfered or updated by ha sync.', + ), + ra_mode=dict( + type='list', elements='str', required=False, + options=['ra-only', 'slaac', 'ra-names', 'ra-stateless', 'ra-advrouter', 'off-link'], + description='Control how IPv6 clients receive their addresses.', + ), + ra_priority=dict( + type='str', required=False, options=['', 'high', 'low'], default='', + description='Priority of the RA announcements.', + ), + ra_mtu=dict( + type='int', required=False, + description='MTU to send to clients via Router Advertisements.', + ), + ra_interval=dict( + type='int', required=False, default=60, + description='Time (seconds) between Router Advertisements.', + ), + ra_router_lifetime=dict( + type='int', required=False, default=1200, + description='Lifetime of the route.', + ), + **STATE_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Range(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_tag.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_tag.py new file mode 100644 index 0000000..86aa5ca --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/dnsmasq_tag.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/dnsmasq.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_tag import Tag + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/dnsmasq.html' + + +def run_module(): + module_args = dict( + tag=dict( + type='str', required=True, aliases=['t', 'name', 'n'], + description='DHCP tag.', + ), + **STATE_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Tag(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bfd_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bfd_general.py new file mode 100644 index 0000000..490a6cc --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bfd_general.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/wireguard.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session + from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_true, to_digit + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bfd.html#oxlorg-opnsense-frr-bfd-general' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bfd.html#id1' + + +def run_module(): + module_args = dict( + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {'enabled': module.params['enabled']}, + } + ) + + with Session(module=module) as s: + is_enabled = is_true( + s.get(cnf={ + 'module': 'quagga', + 'controller': 'bfd', + 'command': 'get', + })['bfd']['enabled'] + ) + result['diff']['before']['enabled'] = is_enabled + + if is_enabled != module.params['enabled']: + result['changed'] = True + + if not module.check_mode: + s.post(cnf={ + 'module': 'quagga', + 'controller': 'bfd', + 'command': 'set', + 'data': {'bfd': {'enabled': to_digit(module.params['enabled'])}} + }) + s.post(cnf={ + 'module': 'quagga', + 'controller': 'service', + 'command': 'reconfigure', + }) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bfd_neighbor.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bfd_neighbor.py new file mode 100644 index 0000000..c16f54d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bfd_neighbor.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bfd_neighbor import Neighbor + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bfd.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bfd.html' + + +def run_module(): + module_args = dict( + ip=dict(type='str', required=True, aliases=[ + 'neighbor', 'peer', 'peer_ip', 'address', + ]), + description=dict(type='str', required=False, aliases=['desc']), + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Neighbor(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_as_path.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_as_path.py new file mode 100644 index 0000000..1c2f4a3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_as_path.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_as_path import AsPath + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-as-path' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#id6' + + +def run_module(): + module_args = dict( + description=dict(type='str', required=True, aliases=['desc']), + number=dict( + type='str', required=False, aliases=['nr'], + description='The ACL rule number (10-99); keep in mind that there are no ' + 'sequence numbers with AS-Path lists. When you want to add a ' + 'new line between you have to completely remove the ACL!' + ), + action=dict(type='str', required=False, options=['permit', 'deny']), + as_pattern=dict( + type='str', required=False, aliases=['as'], + description="The AS pattern you want to match, regexp allowed (e.g. .$ or _1$). " + "It's not validated so please be careful!" + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(AsPath(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_community_list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_community_list.py new file mode 100644 index 0000000..67e1a81 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_community_list.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_community_list import Community + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html +# #oxlorg-opnsense-frr-bgp-community-list' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#id5' + + +def run_module(): + module_args = dict( + description=dict(type='str', required=True, aliases=['desc']), + number=dict(type='str', required=False, aliases=['nr']), + seq=dict(type='int', required=False, aliases=['seq_number']), + action=dict(type='str', required=False, options=['permit', 'deny']), + community=dict( + type='str', required=False, aliases=['comm'], + description='The community you want to match. You can also regex and it is ' + 'not validated so please be careful.' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Community(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_general.py new file mode 100644 index 0000000..d3a1ff7 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_general.py @@ -0,0 +1,84 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-general' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#id1' + + +def run_module(): + module_args = dict( + as_number=dict(type='int', required=True, aliases=['as', 'as_nr']), + distance=dict(type='int', required=False), + id=dict(type='str', required=False, aliases=['router_id']), + graceful=dict( + type='bool', required=False, default=False, + description='BGP graceful restart functionality as defined in ' + 'RFC-4724 defines the mechanisms that allows BGP speaker ' + 'to continue to forward data packets along known routes ' + 'while the routing protocol information is being restored' + ), + networks=dict( + type='list', elements='str', required=False, default=[], + aliases=['nets'], + description='Select the network to advertise, you have to set a ' + 'Null route via System -> Routes' + ), + network_import_check=dict( + type='bool', required=False, default=True, aliases=['network_check'], + description="When enabled (default), BGP only announces networks set at 'Network' " + "if they are present in the routers routing table (alternatively, " + "you can also set a null-route via System -> Routes). If disabled, " + "all configured networks will be announced." + ), + log_neighbor_changes=dict( + type='bool', required=False, default=False, aliases=['log_neigh'], + description='Enable extended logging of BGP neighbor changes' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_neighbor.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_neighbor.py new file mode 100644 index 0000000..1331386 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_neighbor.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_neighbor import Neighbor + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-neighbor' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#id2' + + +def run_module(): + module_args = dict( + ip=dict( + type='str', required=False, + aliases=['neighbor', 'peer', 'peer_ip', 'address'], + ), + as_number=dict( + type='int', required=False, aliases=['as', 'as_nr', 'remote_as'] + ), + remote_as_mode=dict( + type='str', required=False, aliases=['as_mode'], + choices=['internal', 'external'], + description='Set a (MD5-hashed) password for BGP authentication.' + ), + password=dict( + type='str', required=False, aliases=['pwd'], no_log=True, + description='Set a (MD5-hashed) password for BGP authentication.' + ), + weight=dict(type='int', required=False), + local_ip=dict( + type='str', required=False, aliases=['local'], + description='Set the local IP connecting to the neighbor. ' + 'This is only required for BGP authentication.' + ), + source_int=dict( + type='str', required=False, + aliases=['update_source', 'update_src', 'src_int'], + description='Physical name of the IPv4 interface facing the peer' + ), + ipv6_link_local_int=dict( + type='str', required=False, + aliases=['link_local_int', 'ipv6_ll_int', 'v6_ll_int'], + description='Interface to use for IPv6 link-local neighbours' + ), + next_hop_self=dict( + type='bool', required=False, default=False, aliases=['nhs'] + ), + next_hop_self_all=dict( + type='bool', required=False, default=False, aliases=['nhsa'], + description='Add the parameter "all" after next-hop-self command' + ), + multi_hop=dict( + type='bool', required=False, default=False, + description='Specifying ebgp-multihop allows sessions with ' + 'eBGP neighbors to establish when they are multiple ' + 'hops away. When the neighbor is not directly connected ' + 'and this knob is not enabled, the session will not establish.' + ), + multi_protocol=dict( + type='bool', required=False, default=False, + description='Is this neighbour multiprotocol capable per RFC 2283' + ), + rrclient=dict( + type='bool', required=False, default=False, + aliases=['route_reflector_client'] + ), + bfd=dict( + type='bool', required=False, default=False, + description='Enable BFD support for this neighbor.' + ), + send_default_route=dict( + type='bool', required=False, default=False, aliases=['default_originate'], + ), + as_override=dict( + type='bool', required=False, default=False, aliases=['asoverride'], + description='Override AS number of the originating router with the local ' + 'AS number. This command is only allowed for eBGP peers.' + ), + disable_connected_check=dict( + type='bool', required=False, default=False, + description='Allow peerings between directly connected eBGP peers using ' + 'loopback addresses.' + ), + keepalive=dict( + type='int', required=False, default=60, aliases=['keep_alive'], + description='Keepalive timer to check if the neighbor is still up.' + ), + hold_down=dict( + type='int', required=False, default=180, aliases=['holddown'], + description='The time in seconds when a neighbor is considered dead. ' + 'This is usually 3 times the keepalive timer' + ), + connect_timer=dict( + type='int', required=False, aliases=['connecttimer'], + description='The time in seconds how fast a neighbor tries to reconnect.' + ), + description=dict(type='str', required=False, aliases=['desc']), + prefix_list_in=dict( + type='str', required=False, aliases=['prefix_in', 'pre_in'] + ), + prefix_list_out=dict( + type='str', required=False, aliases=['prefix_out', 'pre_out'] + ), + route_map_in=dict( + type='str', required=False, aliases=['map_in', 'rm_in'] + ), + route_map_out=dict( + type='str', required=False, aliases=['map_out', 'rm_out'] + ), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured neighbors with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=[ + 'ip', 'as_number', 'weight', 'local_ip', 'source_int', + 'ipv6_link_local_int', 'disable_connected_check', 'description', + 'prefix_list_in', 'prefix_list_out', 'route_map_in', 'route_map_out', + ], + default=['ip', 'description'], + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Neighbor(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_peer_group.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_peer_group.py new file mode 100644 index 0000000..6791407 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_peer_group.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_peer_group import PeerGroup + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-neighbor' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#id2' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description=' Name of the peer group.' + ), + as_mode=dict( + type='str', required=False, aliases=['remote_as_mode'], + choices=['', 'internal', 'external'] + ), + as_number=dict( + type='int', required=False, aliases=['as', 'as_nr', 'remote_as'], + ), + source_int=dict( + type='str', required=False, + aliases=['update_source', 'update_src', 'src_int'], + description='Physical name of the IPv4 interface facing the peer', + ), + next_hop_self=dict( + type='bool', required=False, default=False, aliases=['nhs'], + description='Sets the local router as the next hop for routes advertised to the neighbor, ' + 'commonly used in Route Reflector setups.', + ), + send_default_route=dict( + type='bool', required=False, default=False, aliases=['default_originate'], + description='Enable sending of default routes to the peer group.', + ), + prefix_list_in=dict( + type='str', required=False, aliases=['prefix_in', 'pre_in'], + description='Prefix list to filter inbound prefixes from this neighbor.', + ), + prefix_list_out=dict( + type='str', required=False, aliases=['prefix_out', 'pre_out'], + description='Prefix list to filter outbound prefixes sent to this neighbor.', + ), + route_map_in=dict( + type='str', required=False, aliases=['map_in', 'rm_in'], + description='Route-map to apply to routes received from this neighbor.', + ), + route_map_out=dict( + type='str', required=False, aliases=['map_out', 'rm_out'], + description='Route-map to apply to routes advertised to this neighbor.', + ), + listen_ranges=dict( + type='list', elements='str', required=False, aliases=['ranges'], default=[], + description='One or multiple valid IP networks in CIDR notation.', + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('as_mode', 'as_number'), True), + ], + mutually_exclusive=[ + ('as_mode', 'as_number'), + ] + ) + + module_wrapper(PeerGroup(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_prefix_list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_prefix_list.py new file mode 100644 index 0000000..1a9deb5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_prefix_list.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_prefix_list import Prefix + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html +# #oxlorg-opnsense-frr-bgp-prefix-list' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#id3' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + seq=dict(type='str', required=True, aliases=['sequence', 'seq_number']), + network=dict(type='str', required=False, aliases=['net']), + description=dict(type='str', required=False, aliases=['desc']), + version=dict( + type='str', required=False, default='IPv4', options=['IPv4', 'IPv6'], + aliases=['ipv'] + ), + action=dict(type='str', required=False, options=['permit', 'deny']), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Prefix(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_redistribution.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_redistribution.py new file mode 100644 index 0000000..f083862 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_redistribution.py @@ -0,0 +1,62 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_redistribution import Redistribution + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-redistribution' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#id2' + + +def run_module(): + module_args = dict( + redistribute=dict(type='str', required=False, choices=['ospf', 'connected', 'kernel', 'rip', 'static']), + description=dict(type='str', required=False, aliases=['desc']), + route_map=dict( + type='str', required=False, aliases=['map', 'rm'] + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Redistribution(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_route_map.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_route_map.py new file mode 100644 index 0000000..754af70 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_bgp_route_map.py @@ -0,0 +1,92 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_route_map import RouteMap + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#oxlorg-opnsense-frr-bgp-route-map' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html#id4' + + +def run_module(): + module_args = dict( + description=dict(type='str', required=False, aliases=['desc']), + name=dict(type='str', required=True), + action=dict(type='str', required=False, options=['permit', 'deny']), + id=dict( + type='int', required=False, + description='Route-map ID between 10 and 99. Be aware that the sorting ' + 'will be done under the hood, so when you add an entry between ' + "it get's to the right position" + ), + as_path_list=dict( + type='list', elements='str', required=False, default=[], aliases=['as_path'] + ), + prefix_list=dict( + type='dict', required=False, default={}, aliases=['prefix', 'pre'], + description='Dictionary of prefixes to link. Per example: ' + "\"{prefix_name: [seq1, seq2]}\" or \"{'pre1': [5, 6]}\" will link " + "prefixes with the name 'pre1' and sequence 5-6" + ), + community_list=dict( + type='list', elements='str', required=False, default=[], aliases=['community'] + ), + set=dict( + type='str', required=False, + description='Free text field for your set, please be careful! ' + 'You can set e.g. "local-preference 300" or "community 1:1" ' + '(http://www.nongnu.org/quagga/docs/docs-multi/' + 'Route-Map-Set-Command.html#Route-Map-Set-Command)' + ), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured PSK with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['name', 'id'], + default=['name', 'id'], + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(RouteMap(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_diagnostic.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_diagnostic.py new file mode 100644 index 0000000..18751da --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_diagnostic.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import single_get + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_diagnostic.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_diagnostic.html' + + +def run_module(): + module_args = dict( + target=dict( + type='str', required=True, + choices=[ + 'bgpneighbors', 'bgproute', 'bgproute4', 'bgproute6', 'bgpsummary', + 'generalroute', 'generalroute4', 'generalroute6', 'generalrunningconfig', + 'ospfdatabase', 'ospfinterface', 'ospfneighbor', 'ospfoverview', 'ospfroute', + 'ospfv3database', 'ospfv3interface', 'ospfv3neighbor', 'ospfv3overview', + 'ospfv3route', + ], + description='What information to query' + ), + **OPN_MOD_ARGS, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + non_json = ['generalrunningconfig'] + + if module.params['target'] in non_json: + params = [] + + else: + params = ['$format=”json”'] + + info = single_get( + module=module, + cnf={ + 'module': 'quagga', + 'controller': 'diagnostics', + 'command': module.params['target'], + 'params': params, + } + ) + + if 'response' in info: + info = info['response'] + + if isinstance(info, str): + info = info.strip() + + module.exit_json(data=info) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_general.py new file mode 100644 index 0000000..97813ad --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_general.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_general.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_general.html' + + +def run_module(): + module_args = dict( + carp=dict( + type='bool', required=False, default=False, aliases=['carp_failover'], + description='Will activate the routing service only on the primary device' + ), + profile=dict( + type='str', required=False, default='traditional', + options=['traditional', 'datacenter'], + description="The 'datacenter' profile is more aggressive. " + "Please refer to the FRR documentation for more information" + ), + snmp_agentx=dict( + type='bool', required=False, default=False, + description='En- or disable support for Net-SNMP AgentX' + ), + log=dict( + type='bool', required=False, default=True, aliases=['logging'], + ), + log_level=dict( + type='str', required=False, default='notifications', + options=[ + 'critical', 'emergencies', 'errors', 'alerts', 'warnings', 'notifications', + 'informational', 'debugging', + ], + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_general.py new file mode 100644 index 0000000..1dd78d6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_general.py @@ -0,0 +1,69 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' + + +def run_module(): + module_args = dict( + carp=dict( + type='bool', required=False, default=False, aliases=['carp_demote'], + description='Register CARP status monitor, when no neighbors are found, ' + 'consider this node less attractive. This feature needs syslog ' + 'enabled using "Debugging" logging to catch all relevant status ' + 'events. This option is not compatible with "Enable CARP Failover"' + ), + id=dict( + type='str', required=False, aliases=['router_id'], + description='If you have a CARP setup, you may want to configure a router id ' + 'in case of a conflict' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_interface.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_interface.py new file mode 100644 index 0000000..41932c3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_interface.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_interface import Interface + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' + + +def run_module(): + module_args = dict( + interface=dict(type='str', required=True, aliases=['name', 'int']), + area=dict( + type='str', required=False, + description='Area in wildcard mask style like 0.0.0.0 and no decimal 0' + ), + passive=dict(type='bool', required=False, default=False), + cost=dict(type='int', required=False), + cost_demoted=dict(type='int', required=False, default=65535), + carp_depend_on=dict( + type='str', required=False, + description='The carp VHID to depend on, when this virtual address is not in ' + 'master state, the interface cost will be set to the demoted cost' + ), + hello_interval=dict(type='int', required=False, aliases=['hello']), + dead_interval=dict(type='int', required=False, aliases=['dead']), + retransmit_interval=dict(type='int', required=False, aliases=['retransmit']), + transmit_delay=dict(type='int', required=False, aliases=['delay']), + priority=dict(type='int', required=False, aliases=['prio']), + network_type=dict( + type='str', required=False, aliases=['nw_type'], + choices=['broadcast', 'point-to-point'], + ), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured interface with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['interface', 'area', 'passive', 'carp_depend_on', 'network_type'], + default=['interface', 'area'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Interface(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_network.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_network.py new file mode 100644 index 0000000..8100eec --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_network.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_network import Network + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html' + + +def run_module(): + module_args = dict( + ip=dict(type='str', required=True, aliases=['nw_address', 'network_address', 'address']), + mask=dict(type='int', required=True, aliases=['nw_mask', 'network_mask']), + area=dict( + type='str', required=False, + description='Area in wildcard mask style like 0.0.0.0 and no decimal 0. ' + 'Only use Area in Interface tab or in Network tab once' + ), + area_range=dict( + type='str', required=False, + description='Here you can summarize a network for this area like fe80:1234::/56' + ), + prefix_list_in=dict( + type='str', required=False, aliases=['prefix_in', 'pre_in'] + ), + prefix_list_out=dict( + type='str', required=False, aliases=['prefix_out', 'pre_out'] + ), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured network with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['ip', 'mask', 'area', 'area_range'], + default=['ip', 'mask'], + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Network(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_prefix_list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_prefix_list.py new file mode 100644 index 0000000..bb22375 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_prefix_list.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_prefix_list import Prefix + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + seq=dict(type='int', required=False, aliases=['seq_number']), + action=dict(type='str', required=False, options=['permit', 'deny']), + network=dict(type='str', required=False, aliases=['net']), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Prefix(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_redistribution.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_redistribution.py new file mode 100644 index 0000000..9db284f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_redistribution.py @@ -0,0 +1,63 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_redistribution import \ + Redistribution + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf3-redistribution' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html#id2' + + +def run_module(): + module_args = dict( + redistribute=dict(type='str', required=False, choices=['bgp', 'connected', 'kernel', 'rip', 'static']), + description=dict(type='str', required=False, aliases=['desc']), + route_map=dict( + type='str', required=False, aliases=['map', 'rm'] + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Redistribution(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_route_map.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_route_map.py new file mode 100644 index 0000000..07f6ae6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf3_route_map.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_route_map import RouteMap + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + action=dict(type='str', required=False, options=['permit', 'deny']), + id=dict( + type='int', required=False, + description='Route-map ID between 10 and 99. Be aware that the sorting ' + 'will be done under the hood, so when you add an entry between ' + "it get's to the right position" + ), + prefix_list=dict( + type='list', elements='str', required=False, default=[], aliases=['prefix'] + ), + set=dict( + type='str', required=False, + description='Free text field for your set, please be careful! ' + 'You can set e.g. "local-preference 300" or "community 1:1" ' + '(http://www.nongnu.org/quagga/docs/docs-multi/' + 'Route-Map-Set-Command.html#Route-Map-Set-Command)' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(RouteMap(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_general.py new file mode 100644 index 0000000..90727b2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_general.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' + + +def run_module(): + module_args = dict( + carp=dict( + type='bool', required=False, default=False, aliases=['carp_demote'], + description='Register CARP status monitor, when no neighbors are found, ' + 'consider this node less attractive. This feature needs syslog ' + 'enabled using "Debugging" logging to catch all relevant status ' + 'events. This option is not compatible with "Enable CARP Failover"' + ), + id=dict( + type='str', required=False, aliases=['router_id'], + description='If you have a CARP setup, you may want to configure a router id ' + 'in case of a conflict' + ), + cost=dict( + type='int', required=False, + aliases=['reference_cost', 'ref_cost'], + description='Here you can adjust the reference cost in Mbps for path calculation. ' + 'Mostly needed when you bundle interfaces to higher bandwidth' + ), + passive_ints=dict( + type='list', elements='str', required=False, default=[], + aliases=['passive_interfaces'], + description='Select the interfaces, where no OSPF packets should be sent to' + ), + originate=dict( + type='bool', required=False, default=False, aliases=['orig', 'advertise_default_gw'], + description='This will send the information that we have a default gateway' + ), + originate_always=dict( + type='bool', required=False, default=False, + aliases=['orig_always', 'always_advertise_default_gw'], + description='This will send the information that we have a default gateway, ' + 'regardless of if it is available' + ), + originate_metric=dict( + type='int', required=False, aliases=['orig_metric'], + description='This let you manipulate the metric when advertising default gateway' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_interface.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_interface.py new file mode 100644 index 0000000..ff32826 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_interface.py @@ -0,0 +1,88 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_interface import Interface + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' + + +def run_module(): + module_args = dict( + interface=dict(type='str', required=True, aliases=['name', 'int']), + auth_type=dict(type='str', required=False, choices=['message-digest']), + auth_key=dict(type='str', required=False, no_log=True), + auth_key_id=dict(type='int', required=False, default=1), + area=dict( + type='str', required=False, + description='Area in wildcard mask style like 0.0.0.0 and no decimal 0' + ), + cost=dict(type='int', required=False), + cost_demoted=dict(type='int', required=False, default=65535), + carp_depend_on=dict( + type='str', required=False, + description='The carp VHID to depend on, when this virtual address is not in ' + 'master state, the interface cost will be set to the demoted cost' + ), + hello_interval=dict(type='int', required=False, aliases=['hello']), + dead_interval=dict(type='int', required=False, aliases=['dead']), + retransmit_interval=dict(type='int', required=False, aliases=['retransmit']), + transmit_delay=dict(type='int', required=False, aliases=['delay']), + priority=dict(type='int', required=False, aliases=['prio']), + network_type=dict( + type='str', required=False, aliases=['nw_type'], + choices=['broadcast', 'non-broadcast', 'point-to-multipoint', 'point-to-point'], + ), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured interface with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['interface', 'area', 'passive', 'carp_depend_on', 'network_type'], + default=['interface', 'area'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Interface(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_network.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_network.py new file mode 100644 index 0000000..283ab1e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_network.py @@ -0,0 +1,81 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_network import Network + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_bgp.html' + + +def run_module(): + module_args = dict( + ip=dict(type='str', required=True, aliases=['nw_address', 'network_address', 'address']), + mask=dict(type='int', required=True, aliases=['nw_mask', 'network_mask']), + area=dict( + type='str', required=False, + description='Area in wildcard mask style like 0.0.0.0 and no decimal 0. ' + 'Only use Area in Interface tab or in Network tab once' + ), + area_range=dict( + type='str', required=False, + description='Here you can summarize a network for this area like 192.168.0.0/23' + ), + prefix_list_in=dict( + type='str', required=False, aliases=['prefix_in', 'pre_in'] + ), + prefix_list_out=dict( + type='str', required=False, aliases=['prefix_out', 'pre_out'] + ), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured network with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['ip', 'mask', 'area', 'area_range'], + default=['ip', 'mask'], + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Network(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_prefix_list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_prefix_list.py new file mode 100644 index 0000000..5dacaa1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_prefix_list.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_prefix_list import Prefix + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + seq=dict(type='int', required=False, aliases=['seq_number']), + action=dict(type='str', required=False, options=['permit', 'deny']), + network=dict(type='str', required=False, aliases=['net']), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Prefix(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_redistribution.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_redistribution.py new file mode 100644 index 0000000..fdbabb9 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_redistribution.py @@ -0,0 +1,62 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_redistribution import Redistribution + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html#oxlorg-opnsense-frr-ospf-redistribution' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html#id2' + + +def run_module(): + module_args = dict( + redistribute=dict(type='str', required=False, choices=['bgp', 'connected', 'kernel', 'rip', 'static']), + description=dict(type='str', required=False, aliases=['desc']), + route_map=dict( + type='str', required=False, aliases=['map', 'rm'] + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Redistribution(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_route_map.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_route_map.py new file mode 100644 index 0000000..808ee58 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_ospf_route_map.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_route_map import RouteMap + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_ospf.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + action=dict(type='str', required=False, options=['permit', 'deny']), + id=dict( + type='int', required=False, + description='Route-map ID between 10 and 99. Be aware that the sorting ' + 'will be done under the hood, so when you add an entry between ' + "it get's to the right position" + ), + prefix_list=dict( + type='list', elements='str', required=False, default=[], aliases=['prefix'] + ), + set=dict( + type='str', required=False, + description='Free text field for your set, please be careful! ' + 'You can set e.g. "local-preference 300" or "community 1:1" ' + '(http://www.nongnu.org/quagga/docs/docs-multi/' + 'Route-Map-Set-Command.html#Route-Map-Set-Command)' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(RouteMap(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_rip.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_rip.py new file mode 100644 index 0000000..bd86f56 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/frr_rip.py @@ -0,0 +1,78 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/quagga.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_rip import Rip + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/frr_rip.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/frr_rip.html' + + +def run_module(): + module_args = dict( + version=dict(type='int', required=False, default=2, aliases=['v']), + metric=dict( + type='int', required=False, aliases=['m', 'default_metric'], + description='Set the default metric to a value between 1 and 16' + ), + passive_ints=dict( + type='list', elements='str', required=False, default=[], + aliases=['passive_interfaces'], + description='Select the interfaces, where no RIP packets should be sent to' + ), + networks=dict( + type='list', elements='str', required=False, default=[], + aliases=['nets'], + description='Enter your networks in CIDR notation' + ), + redistribute=dict( + type='list', elements='str', required=False, default=[], + options=['bgp', 'ospf', 'connected', 'kernel', 'static'], + description='Select other routing sources, which should be ' + 'redistributed to the other nodes' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Rip(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/gateway.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/gateway.py new file mode 100644 index 0000000..fcf1f83 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/gateway.py @@ -0,0 +1,169 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/routing.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.gateway import Gw + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/routing.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/routing.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=False, + description='Gateway Name' + ), + interface=dict( + type='str', required=False, aliases=['int', 'if'], + description='Interface for Gateway' + ), + ip_protocol=dict( + type='str', required=False, choices=['inet', 'inet6'], + description='The Internet Protocol this gateway uses.', + default='inet' + ), + gateway=dict( + type='str', required=False, aliases=['gw', 'ip'], + description='Gateway IP' + ), + default_gw=dict( + type='bool', required=False, aliases=['default', 'internet', 'inet'], + description='This will select the above gateway as a default gateway candidate.', + default=False + ), + far_gw=dict( + type='bool', required=False, aliases=['far'], + description='This will allow the gateway to exist outside of the interface subnet.', + default=False + ), + monitor_disable=dict( + type='bool', required=False, + description='This will consider this gateway as always being "up".', + default=True + ), + monitor_noroute=dict( + type='bool', required=False, + description='Do not create a dedicated host route for this monitor.', + default=False + ), + monitor=dict( + type='str', required=False, + description='Enter an alternative address here to be used to monitor the link. ' + 'This is used for the quality RRD graphs as well as the load balancer entries. ' + 'Use this if the gateway does not respond to ICMP echo requests (pings).' + ), + force_down=dict( + type='bool', required=False, aliases=['down'], + description='This will force this gateway to be considered "down".', + default=False + ), + priority=dict( + type='int', required=False, aliases=['prio'], + description='Choose a value between 1 and 255. Influences sort order when selecting a (default) gateway, ' + 'lower means more important. Default is 255.', + default=255 + ), + weight=dict( + type='int', required=False, + description='Weight for this gateway when used in a gateway group. ' + 'Specificed as an integer number between 1 and 5. Default equals 1.', + default=1 + ), + latency_low=dict( + type='int', required=False, + description='Low threshold for latency in milliseconds. Default is 200.', + default=200 + ), + latency_high=dict( + type='int', required=False, + description='High threshold for latency in milliseconds. Default is 500.', + default=500 + ), + loss_low=dict( + type='int', required=False, + description='Low threshold for packet loss in %. Default is 10.', + default=10 + ), + loss_high=dict( + type='int', required=False, + description='High thresholds for packet loss in %. Default is 20.', + default=20 + ), + interval=dict( + type='int', required=False, + description='How often that an ICMP probe will be sent in seconds. Default is 1.', + default=1 + ), + time_period=dict( + type='int', required=False, + description='The time period over which results are averaged. Default is 60.', + default=60 + ), + loss_interval=dict( + type='int', required=False, + description='Time interval before packets are treated as lost. ' + 'Default is 4 (four times the probe interval).', + default=4 + ), + data_length=dict( + type='int', required=False, + description='Specify the number of data bytes to be sent. Default is 1.', + default=1 + ), + description=dict(type='str', required=False, aliases=['desc']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured gateways with the running config - ' + "if any of those fields are changed, the module will think it's a new gateway", + choices=['name', 'gateway', 'description'], + default=['name', 'gateway'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('gateway', 'ip_protocol'), True), + ], + ) + + module_wrapper(Gw(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/group.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/group.py new file mode 100644 index 0000000..a9e1670 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/group.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/auth.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.group import Group + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/auth.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/auth.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['n'], + description='Group name', + ), + description=dict(type='str', required=False, aliases=['desc']), + member=dict( + type='list', required=False, aliases=['m'], elements='str', + default=[], + ), + privilege=dict( + type='list', required=False, aliases=['priv', 'p'], elements='str', + default=[], + ), + source_net=dict( + type='list', required=False, aliases=['source', 'src', 's'], elements='str', default=[], + description='List of networks which constraint the membership of this group to their location.', + ), + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Group(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_acl.py new file mode 100644 index 0000000..ad0e019 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_acl.py @@ -0,0 +1,379 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_acl import HaproxyAcl + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this condition' + ), + description=dict( + type='str', required=False, default='', + description='Description for this condition' + ), + expression=dict( + type='str', required=False, default='', + choices=['http_auth', 'hdr_beg', 'hdr_end', 'hdr', 'hdr_reg', 'hdr_sub', 'path_beg', 'path_end', + 'path', 'path_reg', 'path_dir', 'path_sub', 'cust_hdr_beg', 'cust_hdr_end', 'cust_hdr', + 'cust_hdr_reg', 'cust_hdr_sub', 'url_param', 'ssl_c_verify', 'ssl_c_verify_code', + 'ssl_c_ca_commonname', 'ssl_hello_type', 'src', 'src_is_local', 'src_port', + 'src_bytes_in_rate', 'src_bytes_out_rate', 'src_kbytes_in', 'src_kbytes_out', + 'src_conn_cnt', 'src_conn_cur', 'src_conn_rate', 'src_http_err_cnt', 'src_http_err_rate', + 'src_http_req_cnt', 'src_http_req_rate', 'src_sess_cnt', 'src_sess_rate', 'nbsrv', + 'traffic_is_http', 'traffic_is_ssl', 'ssl_fc', 'ssl_fc_sni', 'ssl_sni', 'ssl_sni_sub', + 'ssl_sni_beg', 'ssl_sni_end', 'ssl_sni_reg', 'custom_acl', ''], + description='Select condition type to evaluate for this ACL rule' + ), + negate=dict( + type='bool', required=False, default=False, + description='Use this to invert the meaning of the expression' + ), + case_sensitive=dict( + type='bool', required=False, default=False, + description='Enable to make the condition case-sensitive' + ), + custom_acl=dict( + type='str', required=False, default='', + description='Custom HAProxy condition/ACL syntax not supported by ' + 'other expression types (option pass-through)' + ), + # Host-related fields + hdr_beg=dict( + type='str', required=False, default='', + description='HTTP host header starts with string' + ), + hdr_end=dict( + type='str', required=False, default='', + description='HTTP host header ends with string' + ), + hdr=dict( + type='str', required=False, default='', + description='HTTP host header matches exact string' + ), + hdr_reg=dict( + type='str', required=False, default='', + description='HTTP host header matches regular expression' + ), + hdr_sub=dict( + type='str', required=False, default='', + description='HTTP host header contains string' + ), + # Path-related fields + path_beg=dict( + type='str', required=False, default='', + description='HTTP request URL path starts with string' + ), + path_end=dict( + type='str', required=False, default='', + description='HTTP request URL path ends with string' + ), + path=dict( + type='str', required=False, default='', + description='HTTP request URL path matches exact string' + ), + path_reg=dict( + type='str', required=False, default='', + description='HTTP request URL path matches regular expression' + ), + path_dir=dict( + type='str', required=False, default='', + description='HTTP request URL path contains directory' + ), + path_sub=dict( + type='str', required=False, default='', + description='HTTP request URL path contains string' + ), + # SSL-related fields + ssl_c_verify_code=dict( + type='int', required=False, default=0, + description='SSL Client certificate verify error result' + ), + ssl_c_ca_commonname=dict( + type='str', required=False, default='', + description='SSL Client certificate issued by CA common-name' + ), + ssl_hello_type=dict( + type='str', required=False, default=None, + choices=['x0', 'x1', 'x2'], + description='SSL Hello Type: x0 (no client hello), x1 (client hello) [default], ' + 'x2 (server hello)' + ), + # Source IP fields + src=dict( + type='str', required=False, default='', + description='Source IP matches specified IP' + ), + src_port=dict( + type='int', required=False, default=0, + description='Source IP: TCP source port' + ), + src_port_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source port comparison operator: gt (greater than), ge (greater equal), ' + 'eq (equal), lt (less than), le (less equal)' + ), + # Backend-related fields + nbsrv=dict( + type='int', required=False, default=0, + description='Minimum number of usable servers in backend' + ), + nbsrv_backend=dict( + type='str', required=False, default=None, + description='Backend for server count check' + ), + # SNI fields + ssl_fc_sni=dict( + type='str', required=False, default='', + description='SNI TLS extension matches (locally deciphered)' + ), + ssl_sni=dict( + type='str', required=False, default='', + description='SNI TLS extension matches (TCP request content inspection)' + ), + ssl_sni_sub=dict( + type='str', required=False, default='', + description='SNI TLS extension contains (TCP request content inspection)' + ), + ssl_sni_beg=dict( + type='str', required=False, default='', + description='SNI TLS extension starts with (TCP request content inspection)' + ), + ssl_sni_end=dict( + type='str', required=False, default='', + description='SNI TLS extension ends with (TCP request content inspection)' + ), + ssl_sni_reg=dict( + type='str', required=False, default='', + description='SNI TLS extension regex (TCP request content inspection)' + ), + # Custom header fields + cust_hdr_beg_name=dict( + type='str', required=False, default='', + description='HTTP header name to check for cust_hdr_beg expression type' + ), + cust_hdr_beg=dict( + type='str', required=False, default='', + description='String that the HTTP header value must start with for cust_hdr_beg expression' + ), + cust_hdr_end_name=dict( + type='str', required=False, default='', + description='HTTP header name to check for cust_hdr_end expression type' + ), + cust_hdr_end=dict( + type='str', required=False, default='', + description='String that the HTTP header value must end with for cust_hdr_end expression' + ), + cust_hdr_name=dict( + type='str', required=False, default='', + description='HTTP header name to check for cust_hdr expression type' + ), + cust_hdr=dict( + type='str', required=False, default='', + description='Exact string that the HTTP header value must match for cust_hdr expression' + ), + cust_hdr_reg_name=dict( + type='str', required=False, default='', + description='HTTP header name to check for cust_hdr_reg expression type' + ), + cust_hdr_reg=dict( + type='str', required=False, default='', + description='Regular expression that the HTTP header value must match for cust_hdr_reg expression' + ), + cust_hdr_sub_name=dict( + type='str', required=False, default='', + description='HTTP header name to check for cust_hdr_sub expression type' + ), + cust_hdr_sub=dict( + type='str', required=False, default='', + description='String that the HTTP header value must contain for cust_hdr_sub expression' + ), + # URL parameter fields + url_param=dict( + type='str', required=False, default='', + description='URL parameter name to check for url_param expression type' + ), + url_param_value=dict( + type='str', required=False, default='', + description='URL parameter value to match for url_param expression type' + ), + # Auth fields + allowed_users=dict( + type='list', required=False, default=[], + description='Select one or more users for HTTP Basic Auth' + ), + allowed_groups=dict( + type='list', required=False, default=[], + description='Select one or more groups for HTTP Basic Auth' + ), + # Source IP metrics with comparisons + src_bytes_in_rate_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP incoming bytes rate comparison operator (gt/ge/eq/lt/le)' + ), + src_bytes_in_rate=dict( + type='int', required=False, default=0, + description='Source IP incoming bytes rate threshold' + ), + src_bytes_out_rate_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP outgoing bytes rate comparison operator (gt/ge/eq/lt/le)' + ), + src_bytes_out_rate=dict( + type='int', required=False, default=0, + description='Source IP outgoing bytes rate threshold' + ), + src_conn_cnt_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP connection count comparison operator (gt/ge/eq/lt/le)' + ), + src_conn_cnt=dict( + type='int', required=False, default=0, + description='Source IP cumulative number of connections threshold' + ), + src_conn_cur_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP current connections comparison operator (gt/ge/eq/lt/le)' + ), + src_conn_cur=dict( + type='int', required=False, default=0, + description='Source IP concurrent connections threshold' + ), + src_conn_rate_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP connection rate comparison operator (gt/ge/eq/lt/le)' + ), + src_conn_rate=dict( + type='int', required=False, default=0, + description='Source IP connection rate threshold' + ), + src_http_err_cnt_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP HTTP error count comparison operator (gt/ge/eq/lt/le)' + ), + src_http_err_cnt=dict( + type='int', required=False, default=0, + description='Source IP cumulative number of HTTP errors threshold' + ), + src_http_err_rate_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP HTTP error rate comparison operator (gt/ge/eq/lt/le)' + ), + src_http_err_rate=dict( + type='int', required=False, default=0, + description='Source IP rate of HTTP errors threshold' + ), + src_http_req_cnt_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP HTTP request count comparison operator (gt/ge/eq/lt/le)' + ), + src_http_req_cnt=dict( + type='int', required=False, default=0, + description='Source IP number of HTTP requests threshold' + ), + src_http_req_rate_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP HTTP request rate comparison operator (gt/ge/eq/lt/le)' + ), + src_http_req_rate=dict( + type='int', required=False, default=0, + description='Source IP rate of HTTP requests threshold' + ), + src_kbytes_in_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP kilobytes in comparison operator (gt/ge/eq/lt/le)' + ), + src_kbytes_in=dict( + type='int', required=False, default=0, + description='Source IP amount of data received in kilobytes threshold' + ), + src_kbytes_out_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP kilobytes out comparison operator (gt/ge/eq/lt/le)' + ), + src_kbytes_out=dict( + type='int', required=False, default=0, + description='Source IP amount of data sent in kilobytes threshold' + ), + src_sess_cnt_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP session count comparison operator (gt/ge/eq/lt/le)' + ), + src_sess_cnt=dict( + type='int', required=False, default=0, + description='Source IP cumulative number of sessions threshold' + ), + src_sess_rate_comparison=dict( + type='str', required=False, default=None, + choices=['gt', 'ge', 'eq', 'lt', 'le'], + description='Source IP session rate comparison operator (gt/ge/eq/lt/le)' + ), + src_sess_rate=dict( + type='int', required=False, default=0, + description='Source IP session rate threshold' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyAcl(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_action.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_action.py new file mode 100644 index 0000000..e72ef1b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_action.py @@ -0,0 +1,299 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_action import HaproxyAction + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this rule' + ), + description=dict( + type='str', required=False, default='', + description='Description for this rule' + ), + test_type=dict( + type='str', required=False, default=None, + choices=['if', 'unless'], + description='Choose how to test the condition. IF [default] tests if condition is true, ' + 'UNLESS tests if condition is false' + ), + linked_acls=dict( + type='list', required=False, default=[], + description='Select one or more conditions to be used for this rule' + ), + operator=dict( + type='str', required=False, default=None, + choices=['and', 'or'], + description='Choose a logical operator to combine conditions: AND [default] or OR' + ), + type=dict( + type='str', required=False, default=None, + choices=['use_backend', 'use_server', 'map_use_backend', 'fcgi_pass_header', 'fcgi_set_param', + 'http-request_allow', 'http-request_deny', 'http-request_tarpit', 'http-request_auth', + 'http-request_redirect', 'http-request_lua', 'http-request_use-service', + 'http-request_add-header', 'http-request_set-header', 'http-request_del-header', + 'http-request_replace-header', 'http-request_replace-value', 'http-request_set-path', + 'http-request_set-var', 'http-response_allow', 'http-response_deny', 'http-response_lua', + 'http-response_add-header', 'http-response_set-header', 'http-response_del-header', + 'http-response_replace-header', 'http-response_replace-value', 'http-response_set-status', + 'http-response_set-var', 'monitor_fail', 'tcp-request_connection_accept', + 'tcp-request_connection_reject', 'tcp-request_content_accept', 'tcp-request_content_reject', + 'tcp-request_content_lua', 'tcp-request_content_use-service', 'tcp-request_inspect-delay', + 'tcp-response_content_accept', 'tcp-response_content_close', 'tcp-response_content_reject', + 'tcp-response_content_lua', 'tcp-response_inspect-delay', 'custom'], + description='Select HAProxy action type to execute when condition matches' + ), + use_backend=dict( + type='str', required=False, default='', + description='HAProxy will use this backend pool if the condition evaluates to true' + ), + use_server=dict( + type='str', required=False, default='', + description='HAProxy will use this server instead of other servers that are specified in the Backend Pool' + ), + custom_rule=dict( + type='str', required=False, default='', + description='Custom rule (option pass-through)' + ), + # FastCGI fields + fcgi_pass_header=dict( + type='str', required=False, default='', + description='FastCGI pass-header' + ), + fcgi_set_param=dict( + type='str', required=False, default='', + description='FastCGI set-param' + ), + # HTTP Request fields + http_request_auth=dict( + type='str', required=False, default='', + description='HTTP request auth realm' + ), + http_request_redirect=dict( + type='str', required=False, default='', + description='HTTP request redirect location' + ), + http_request_lua=dict( + type='str', required=False, default='', + description='HTTP request lua action' + ), + http_request_use_service=dict( + type='str', required=False, default='', + description='HTTP request lua service' + ), + # HTTP Request Header Add + http_request_add_header_name=dict( + type='str', required=False, default='', + description='HTTP request header name to add' + ), + http_request_add_header_content=dict( + type='str', required=False, default='', + description='HTTP request header content to add' + ), + # HTTP Request Header Set + http_request_set_header_name=dict( + type='str', required=False, default='', + description='HTTP request header name to set' + ), + http_request_set_header_content=dict( + type='str', required=False, default='', + description='HTTP request header content to set' + ), + # HTTP Request Header Delete + http_request_del_header_name=dict( + type='str', required=False, default='', + description='HTTP request header name to delete' + ), + # HTTP Request Header Replace + http_request_replace_header_name=dict( + type='str', required=False, default='', + description='HTTP request header name to replace' + ), + http_request_replace_header_regex=dict( + type='str', required=False, default='', + description='HTTP request header regex to replace' + ), + http_request_replace_value_name=dict( + type='str', required=False, default='', + description='HTTP request value name to replace' + ), + http_request_replace_value_regex=dict( + type='str', required=False, default='', + description='HTTP request value regex to replace' + ), + # HTTP Request Set Path + http_request_set_path=dict( + type='str', required=False, default='', + description='HTTP request set-path value' + ), + # HTTP Request Set Variable + http_request_set_var_scope=dict( + type='str', required=False, default=None, + choices=['proc', 'sess', 'txn', 'req', 'res'], + description='Variable scope: proc (whole process), sess (whole session), txn (transaction), ' + 'req (request only), res (response only)' + ), + http_request_set_var_name=dict( + type='str', required=False, default='', + description='HTTP request set-var name' + ), + http_request_set_var_expr=dict( + type='str', required=False, default='', + description='HTTP request set-var expression' + ), + # HTTP Response fields + http_response_lua=dict( + type='str', required=False, default='', + description='HTTP response lua script' + ), + # HTTP Response Header Add + http_response_add_header_name=dict( + type='str', required=False, default='', + description='HTTP response header name to add' + ), + http_response_add_header_content=dict( + type='str', required=False, default='', + description='HTTP response header content to add' + ), + # HTTP Response Header Set + http_response_set_header_name=dict( + type='str', required=False, default='', + description='HTTP response header name to set' + ), + http_response_set_header_content=dict( + type='str', required=False, default='', + description='HTTP response header content to set' + ), + # HTTP Response Header Delete + http_response_del_header_name=dict( + type='str', required=False, default='', + description='HTTP response header name to delete' + ), + # HTTP Response Header Replace + http_response_replace_header_name=dict( + type='str', required=False, default='', + description='HTTP response header name to replace' + ), + http_response_replace_header_regex=dict( + type='str', required=False, default='', + description='HTTP response header regex to replace' + ), + http_response_replace_value_name=dict( + type='str', required=False, default='', + description='HTTP response value name to replace' + ), + http_response_replace_value_regex=dict( + type='str', required=False, default='', + description='HTTP response value regex to replace' + ), + http_response_set_status_code=dict( + type='int', required=False, + description='HTTP response status code (100-999)' + ), + http_response_set_status_reason=dict( + type='str', required=False, default='', + description='HTTP response status reason' + ), + # HTTP Response Set Variable + http_response_set_var_scope=dict( + type='str', required=False, default=None, + choices=['proc', 'sess', 'txn', 'req', 'res'], + description='Variable scope: proc (whole process), sess (whole session), txn (transaction), ' + 'req (request only), res (response only)' + ), + http_response_set_var_name=dict( + type='str', required=False, default='', + description='HTTP response set-var name' + ), + http_response_set_var_expr=dict( + type='str', required=False, default='', + description='HTTP response set-var expression' + ), + # TCP fields + tcp_request_content_lua=dict( + type='str', required=False, default='', + description='TCP request content lua script' + ), + tcp_request_content_use_service=dict( + type='str', required=False, default='', + description='TCP request content use-service' + ), + tcp_request_inspect_delay=dict( + type='str', required=False, default='', + description='TCP request inspect-delay' + ), + tcp_response_content_lua=dict( + type='str', required=False, default='', + description='TCP response content lua script' + ), + tcp_response_inspect_delay=dict( + type='str', required=False, default='', + description='TCP response inspect-delay' + ), + # Monitor fail + monitor_fail_uri=dict( + type='str', required=False, default='', + description='Monitor fail URI' + ), + # Map use backend + map_use_backend_file=dict( + type='str', required=False, default='', + description='Map file for backend selection' + ), + map_use_backend_default=dict( + type='str', required=False, default='', + description='Default backend for map-based selection' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyAction(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_cpu.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_cpu.py new file mode 100644 index 0000000..441dbd2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_cpu.py @@ -0,0 +1,76 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_cpu import HaproxyCpu + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Choose a name for this CPU affinity rule' + ), + thread_id=dict( + type='str', required=False, default=None, + choices=['all', 'odd', 'even'] + [f'x{i}' for i in range(64)], + description='Thread ID that should bind to a specific CPU set' + ), + cpu_id=dict( + type='list', elements='str', required=False, default=None, + choices=['all', 'odd', 'even'] + [f'x{i}' for i in range(64)], + description='Bind the process/thread ID to this CPU' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('thread_id', 'cpu_id')), + ], + ) + + module_wrapper(HaproxyCpu(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_errorfile.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_errorfile.py new file mode 100644 index 0000000..5228c70 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_errorfile.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_errorfile import HaproxyErrorfile + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this error message' + ), + description=dict( + type='str', required=False, default=None, + description='Description for this error message' + ), + code=dict( + type='str', required=False, default='x503', + choices=['x200', 'x400', 'x403', 'x405', 'x408', 'x429', 'x500', 'x502', 'x503', 'x504'], + description='HTTP error status code. Select the HTTP status code this error file should handle. ' + 'Options: x200, x400, x403, x405, x408, x429, x500, x502, x503, x504' + ), + content=dict( + type='str', required=False, default=None, + description='Paste the content of your error messages here' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('content',)), + ], + ) + + module_wrapper(HaproxyErrorfile(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_fcgi.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_fcgi.py new file mode 100644 index 0000000..a518c60 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_fcgi.py @@ -0,0 +1,103 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_fcgi import HaproxyFcgi + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this FastCGI application' + ), + description=dict( + type='str', required=False, default='', + description='Description for this FastCGI application' + ), + docroot=dict( + type='str', required=False, default='', + description='Define the document root on the remote host. ' + 'Used to build SCRIPT_FILENAME and PATH_TRANSLATED parameters' + ), + index=dict( + type='str', required=False, default='', + description='Define the script name that will be appended after a URI' + ), + path_info=dict( + type='str', required=False, default='', + description='Define a regular expression to extract script-name and path-info from URL-decoded path' + ), + log_stderr=dict( + type='bool', required=False, default=False, + description='Enable logging of STDERR messages reported by the FastCGI application' + ), + keep_conn=dict( + type='bool', required=False, default=True, + description='Instruct the FastCGI application to keep connection open' + ), + get_values=dict( + type='bool', required=False, default=False, + description='Enable retrieval of connection management variables by sending FCGI_GET_VALUES on connection' + ), + mpxs_conns=dict( + type='bool', required=False, default=False, + description='Enable support for connection multiplexing' + ), + max_reqs=dict( + type='int', required=False, default=None, + description='Define maximum number of concurrent requests (1-100000)' + ), + linked_actions=dict( + type='list', elements='str', required=False, default=[], + description='Choose FastCGI rules to be included in this FastCGI application' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyFcgi(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_cache.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_cache.py new file mode 100644 index 0000000..c7a6f5c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_cache.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_cache import HaproxyGeneralCache + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + total_max_size=dict( + type='int', required=False, default=4, + description='Define the size in RAM of the cache in megabytes. This size is split in blocks of 1kB' + ), + max_age=dict( + type='int', required=False, default=60, + description='Define the maximum expiration duration. ' + 'Cache-Control response headers will be respected if they are less than this value' + ), + max_object_size=dict( + type='int', required=False, default=None, + description='Define the maximum size of the objects to be cached. ' + 'Must not be greater than an half of the maximum size of the cache' + ), + process_vary=dict( + type='bool', required=False, default=False, + description='Enable or disable the processing of the Vary header. ' + 'When disabled, a response containing such a header will never be cached' + ), + max_secondary_entries=dict( + type='int', required=False, default=10, + description='Define the maximum number of simultaneous secondary entries ' + 'with the same primary key in the cache' + ), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyGeneralCache(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_defaults.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_defaults.py new file mode 100644 index 0000000..d532988 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_defaults.py @@ -0,0 +1,99 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_defaults import \ + HaproxyGeneralDefaults + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + max_connections=dict( + type='int', required=False, default=None, + description='Set the maximum number of concurrent connections for public services' + ), + max_connections_servers=dict( + type='int', required=False, default=None, + description='Set the maximum number of concurrent connections for servers' + ), + timeout_client=dict( + type='str', required=False, default='30s', + description='Set the maximum inactivity time on the client side. Defaults to milliseconds' + ), + timeout_connect=dict( + type='str', required=False, default='30s', + description='Set the maximum time to wait for a connection attempt to a server to succeed' + ), + timeout_check=dict( + type='str', required=False, default=None, + description='Sets an additional read timeout for running health checks on a server' + ), + timeout_server=dict( + type='str', required=False, default='30s', + description='Set the maximum inactivity time on the server side. Defaults to milliseconds' + ), + retries=dict( + type='int', required=False, default=3, + description='Set the number of retries to perform on a server after a connection failure' + ), + redispatch=dict( + type='str', required=False, default='x-1', + choices=['x3', 'x2', 'x1', 'x0', 'x-1', 'x-2', 'x-3'], + description='Enable or disable session redistribution in case of connection failure' + ), + init_addr=dict( + type='list', elements='str', required=False, default=['last', 'libc'], + description='Indicates in which order server addresses should be resolved upon startup' + ), + custom_options=dict( + type='str', required=False, default=None, + description='These lines will be added to the defaults settings of to the HAProxy configuration file' + ), + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyGeneralDefaults(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_logging.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_logging.py new file mode 100644 index 0000000..4145c81 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_logging.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_logging import \ + HaproxyGeneralLogging + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + host=dict( + type='str', required=False, default='127.0.0.1', + description='Indicates where to send the logs. Takes an IPv4 or IPv6 address optionally followed by a colon' + ), + facility=dict( + type='str', required=False, default='local0', + choices=['alert', 'audit', 'auth2', 'auth', 'cron2', 'cron', 'daemon', 'ftp', 'kern', + 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', + 'lpr', 'mail', 'news', 'ntp', 'syslog', 'user', 'uucp'], + description='Choose one of the 24 standard syslog facilities' + ), + level=dict( + type='str', required=False, default='info', + choices=['alert', 'crit', 'debug', 'emerg', 'err', 'info', 'notice', 'warning'], + description='Can be specified to filter outgoing messages. By default, all messages are sent' + ), + length=dict( + type='int', required=False, default=None, + description='Specify an optional maximum line length in characters. Log lines larger than ' + 'this value will be truncated' + ), + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyGeneralLogging(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_peers.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_peers.py new file mode 100644 index 0000000..dbecbcc --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_peers.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_peers import HaproxyGeneralPeers + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name1=dict( + type='str', required=False, default=None, + description='The name of the peer. Usually the fully qualified domain name. If name matches ' + 'system hostname, peer is automatically configured as local' + ), + listen1=dict( + type='str', required=False, default=None, + description='The listen address of the local peer or the address of the remote peer' + ), + port1=dict( + type='int', required=False, default=1024, + description='The TCP port that should be used for connections to this peer. ' + 'It must not be used by any other service' + ), + name2=dict( + type='str', required=False, default=None, + description='The name of the peer. Usually the fully qualified domain name. ' + 'If name matches system hostname, peer is automatically configured as local' + ), + listen2=dict( + type='str', required=False, default=None, + description='The listen address of the local peer or the address of the remote peer' + ), + port2=dict( + type='int', required=False, default=1024, + description='The TCP port that should be used for connections to this peer. ' + 'It must not be used by any other service' + ), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyGeneralPeers(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_settings.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_settings.py new file mode 100644 index 0000000..99a70f7 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_settings.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_settings import \ + HaproxyGeneralSettings + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + graceful_stop=dict( + type='bool', required=False, default=True, + description='Enable graceful stop mode which handles existing connections before stopping' + ), + hard_stop_after=dict( + type='int', required=False, default=None, + description='Maximum time in seconds for a graceful stop, after which HAProxy terminates all connections' + ), + close_spread_time=dict( + type='int', required=False, default=None, + description='Time window in seconds to spread connection closing during graceful shutdown' + ), + seamless_reload=dict( + type='bool', required=False, default=False, + description='Handle restarts without losing connections' + ), + show_intro=dict( + type='bool', required=False, default=True, + description='Show/hide introduction pages' + ), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyGeneralSettings(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_stats.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_stats.py new file mode 100644 index 0000000..c9e41ea --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_stats.py @@ -0,0 +1,105 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_stats import HaproxyGeneralStats + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + port=dict( + type='int', required=False, default=8822, + description='Choose a TCP port to be used for the local statistics page. The default value is 8822' + ), + remote_enabled=dict( + type='bool', required=False, default=False, + description='Enable remote access to HAProxy statistics page. This may be a security risk ' + 'if authentication is not enabled' + ), + remote_bind=dict( + type='list', elements='str', required=False, default=[], + description='Configure listen addresses for the statistics page to enable remote access' + ), + auth_enabled=dict( + type='bool', required=False, default=False, + description='Enable authentication' + ), + users=dict( + type='list', elements='str', required=False, default=[], + description='Allowed users in format user:password' + ), + allowed_users=dict( + type='list', elements='str', required=False, default=[], + description='List of user names that are allowed to access the statistics page. User names will ' + 'be automatically resolved to UUIDs' + ), + allowed_groups=dict( + type='list', elements='str', required=False, default=[], + description='List of group names that are allowed to access the statistics page. Group names will ' + 'be automatically resolved to UUIDs' + ), + custom_options=dict( + type='str', required=False, default=None, + description='These lines will be added to the statistics settings of the HAProxy configuration file' + ), + prometheus_enabled=dict( + type='bool', required=False, default=False, + description='Enable HAProxy Prometheus exporter' + ), + prometheus_bind=dict( + type='list', elements='str', required=False, default=['*:8404'], + description='Configure listen addresses for the prometheus exporter' + ), + prometheus_path=dict( + type='str', required=False, default='/metrics', + description='The path where the Prometheus exporter can be accessed' + ), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyGeneralStats(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_tuning.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_tuning.py new file mode 100644 index 0000000..124f05b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_general_tuning.py @@ -0,0 +1,166 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_tuning import \ + HaproxyGeneralTuning + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + root=dict( + type='bool', required=False, default=False, + description='Enable or disable HAProxy running as user root. Enabling this option is strongly discouraged' + ), + max_connections=dict( + type='int', required=False, default=None, + description='Sets the maximum number of concurrent connections per HAProxy process' + ), + nbthread=dict( + type='int', required=False, default=1, + description='Number of threads to create for each HAProxy process' + ), + resolvers_prefer=dict( + type='str', required=False, default='ipv4', + choices=['ipv4', 'ipv6'], + description='Choose which IP family is preferred when resolving DNS names' + ), + ssl_server_verify=dict( + type='str', required=False, default='ignore', + choices=['ignore', 'required', 'none'], + description='Enforces behavior for SSL verify on servers, ignoring per-server settings' + ), + max_dh_size=dict( + type='int', required=False, default=2048, + description='Sets the maximum size of Diffie-Hellman parameters for ephemeral/temporary key exchange' + ), + buffer_size=dict( + type='int', required=False, default=16384, + description='Change the buffer size in bytes, affecting session coexistence and memory usage' + ), + spread_checks=dict( + type='int', required=False, default=2, + description='Add randomness in the check interval between 0 and +/- 50%' + ), + bogus_proxy_enabled=dict( + type='bool', required=False, default=False, + description='Disables support for HAProxy PROXY protocol in bogus header' + ), + lua_max_mem=dict( + type='int', required=False, default=0, + description='Sets maximum RAM in megabytes per process usable by Lua' + ), + custom_options=dict( + type='str', required=False, default=None, + description='Custom HAProxy options to add to global section' + ), + ocsp_update_enabled=dict( + type='bool', required=False, default=False, + description='Enable automatic OCSP response updates at least once an hour' + ), + ocsp_update_min_delay=dict( + type='int', required=False, default=300, + description='Minimum delay in seconds between two OCSP updates' + ), + ocsp_update_max_delay=dict( + type='int', required=False, default=3600, + description='Maximum delay in seconds between two OCSP updates' + ), + ssl_defaults_enabled=dict( + type='bool', required=False, default=False, + description='Enable global SSL default values with configurable version and cipher options' + ), + ssl_bind_options=dict( + type='list', elements='str', required=False, default=['prefer-client-ciphers'], + description='SSL/TLS binding options' + ), + ssl_min_version=dict( + type='str', required=False, default='TLSv1.2', + choices=['SSLv3', 'TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + description='Minimum SSL/TLS version' + ), + ssl_max_version=dict( + type='str', required=False, default=None, + choices=['SSLv3', 'TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'], + description='Maximum SSL/TLS version' + ), + ssl_cipher_list=dict( + type='str', required=False, default=None, + description='SSL cipher list for TLSv1.2 and below' + ), + ssl_cipher_suites=dict( + type='str', required=False, default=None, + description='SSL cipher suites for TLSv1.3' + ), + h2_initial_window_size=dict( + type='int', required=False, default=None, + description='HTTP/2 initial window size' + ), + h2_initial_window_size_outgoing=dict( + type='int', required=False, default=None, + description='HTTP/2 initial window size for outgoing connections' + ), + h2_initial_window_size_incoming=dict( + type='int', required=False, default=None, + description='HTTP/2 initial window size for incoming connections' + ), + h2_max_concurrent_streams=dict( + type='int', required=False, default=None, + description='HTTP/2 maximum concurrent streams' + ), + h2_max_concurrent_streams_outgoing=dict( + type='int', required=False, default=None, + description='HTTP/2 maximum concurrent streams for outgoing connections' + ), + h2_max_concurrent_streams_incoming=dict( + type='int', required=False, default=None, + description='HTTP/2 maximum concurrent streams for incoming connections' + ), + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyGeneralTuning(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_group.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_group.py new file mode 100644 index 0000000..69a2ab7 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_group.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_group import HaproxyGroup + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this group' + ), + description=dict( + type='str', required=False, default=None, + description='Description for this group' + ), + members=dict( + type='list', elements='str', required=False, default=[], + description='Type username or choose from list. List of user names that are members of this group' + ), + add_userlist=dict( + type='bool', required=False, default=False, + description='Usually HAproxy userlists are created automatically in a context sensitive way. ' + 'This option adds this group as userlist' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyGroup(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_lua.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_lua.py new file mode 100644 index 0000000..8abeb81 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_lua.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_lua import HaproxyLua + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this Lua script' + ), + description=dict( + type='str', required=False, default=None, + description='Description for this Lua script' + ), + preload=dict( + type='bool', required=False, default=True, + description='Whether HAProxy should load and execute this Lua script on startup. ' + 'Set to false when using require() function' + ), + filename_scheme=dict( + type='str', required=False, default='id', + choices=['id', 'name'], + description='Specify the filename scheme for this Lua script. ' + 'Usually using the ID is sufficient and most fail-safe. ' + 'Use name when using require() function' + ), + content=dict( + type='str', required=False, default=None, + description='Paste the content of your Lua script here' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyLua(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_maintenance.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_maintenance.py new file mode 100644 index 0000000..c3f04af --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_maintenance.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_maintenance import HaproxyMaintenance + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + sync_certs=dict( + type='bool', required=False, default=False, + description="Periodically sync SSL certificate changes into the running HAProxy service. " + "Useful for short-lived Let's Encrypt certificates" + ), + reload_service=dict( + type='bool', required=False, default=False, + description='Periodically perform a reload of the HAProxy service. May cause minor service disruption. ' + 'Can apply configuration changes outside business hours' + ), + restart_service=dict( + type='bool', required=False, default=False, + description="Periodically perform a full restart of the HAProxy service. " + "Causes notable service disruption. " + "Required when reload doesn't work due to long-running connections" + ), + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyMaintenance(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_user.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_user.py new file mode 100644 index 0000000..f45eece --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/haproxy_user.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/haproxy.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_user import HaproxyUser + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/haproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Name to identify this user' + ), + description=dict( + type='str', required=False, default=None, + description='Description for this user' + ), + password=dict( + type='str', required=False, default=None, no_log=True, + description='Both encrypted and unencrypted passwords can be used. ' + 'Most systems support MD5, SHA-256, SHA-512, and, of course, ' + 'the classic DES-based method of encrypting passwords' + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(HaproxyUser(module=module, result=result)) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/hasync_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/hasync_general.py new file mode 100644 index 0000000..a790a89 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/hasync_general.py @@ -0,0 +1,107 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.hasync_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/hasync.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/hasync.html' + + +def run_module(): + module_args = dict( + preempt=dict( + type='bool', required=False, default=True, + description='When this device is configured as CARP master it will try to switch to master when powering ' + ' up, this option will keep this one slave if there already is a master on the network.', + ), + disconnect_ppps=dict( + type='bool', required=False, default=False, + description='When this device is configured as CARP backup it will disconnect all PPP type interfaces ' + 'and try to reconnect them when becoming master again.', + ), + pfsync_interface=dict( + type='str', required=False, aliases=['interface', 'i', 'int'], + description='Enable state insertion, update, and deletion messages between firewalls by utilizing the ' + 'selected interface for communication. Best choose a dedicated interface for this type of ' + 'communication to prevent manipulation of states causing security issues.', + ), + pfsync_peer_ip=dict( + type='str', required=False, aliases=['peer'], + description='Force pfsync to synchronize its state table to this IP address.', + ), + pfsync_version=dict( + type='str', required=False, choices=['1301', '1400'], default='1400', + description='Newer versions of OPNsense offer additional attributes in the state synchronization, ' + 'for compatibility reasons you can optionally choose an older version here. Always make sure ' + 'both nodes use the same version to avoid inconsistent state tables.', + ), + synchronize_to_ip=dict( + type='str', required=False, + description='IP address of the firewall to which the selected configuration sections should be ' + 'synchronized. This should be empty on the backup machine. When an IP address is offered, ' + 'both web GUI configurations should be equal (port and protocol).', + ), + verify_peer=dict( + type='bool', required=False, default=False, + description='In most cases the target host will be a directly attached neighbor in which case TLS ' + 'verification can be ignored.', + ), + username=dict( + type='str', required=False, + description='Web GUI username of the system entered for synchronizing your configuration.', + ), + password=dict( + type='str', required=False, no_log=True, + description='Web GUI password of the system entered for synchronizing your configuration.', + ), + update_password=dict( + type='str', required=False, choices=['always', 'on_create'], default='always', + description='Update the password `always` or only `on_create`.', + ), + syncitems=dict( + type='list', required=False, default=[], elements='str', + description='Services that should be send to the other host.', + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/hasync_service.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/hasync_service.py new file mode 100644 index 0000000..85aefea --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/hasync_service.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/hasync.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/hasync.html' + + +# pylint: disable=R0915 +def run_module(): + module_args = dict( + name=dict( + type='str', required=False, default='all', aliases=['service', 'svc', 'target', 'n'], + description='What service to interact with', + ), + action=dict( + type='str', required=True, aliases=['do', 'a'], + choices=['restart', 'start', 'stop'], + description='What action to execute. Some services may not support all of these actions - ' + 'the module will inform you in that case', + ), + ignore_version_mismatch=dict( + type='bool', required=False, default=False, + description='Report a version missmatch as warning and do not fail.', + ), + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + with Session(module=module) as session: + version = session.get(cnf={ + 'module': 'core', + 'controller': 'hasync_status', + 'command': 'version', + }) + + if not isinstance(version.get('response'), dict): + module.fail_json(msg='The backup firewall is not accessible.', resp=version) + + peer_version = version['response'].get('firmware', {}).get('version') + my_version = version['response'].get('firmware', {}).get('_my_version') + + if peer_version != my_version: + if not module.params['ignore_version_mismatch']: + module.fail_json(msg='Remote version differs from this machines, please update first before ' + 'using any of the actions below to avoid breakage.') + module.warn('Remote version differs from this machines, please update first before using any of ' + 'the actions below to avoid breakage.') + + # Restart all + if module.params['name'] == 'all': + if module.params['action'] != 'restart': + module.fail_json('All services only supports action restart') + result['changed'] = True + if not module.check_mode: + session.post(cnf={ + 'module': 'core', + 'controller': 'hasync_status', + 'command': 'restartAll', + }) + module.exit_json(**result) + + # Restart named service(s) + services = session.get(cnf={ + 'module': 'core', + 'controller': 'hasync_status', + 'command': 'services', + }) + + for service in services['rows']: + if service['name'] != module.params['name']: + continue + + if module.params['action'] == 'stop' and service['status'] is False: + continue + if module.params['action'] == 'start' and service['status'] is True: + continue + if module.params['action'] != 'restart' and service.get('nocheck') is True: + module.fail_json(msg=f"{service['description']} can only be restarted") + + result['changed'] = True + if not module.check_mode: + resp = session.post(cnf={ + 'module': 'core', + 'controller': 'hasync_status', + 'command': module.params['action'], + 'params': service['uid'], + }) + if resp['status'] != 'ok': + module.fail_json(msg=f"Action {module.params['action']} for {service['description']} " + f"failed with {resp['status']}") + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_action.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_action.py new file mode 100644 index 0000000..96ca2eb --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_action.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ids.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + single_get, single_post + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ids.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ids.html' + +ACTION_MAPPING = { + 'get_alert_info': {'a': 'getAlertInfo', 'post': False}, + 'get_alert_logs': {'a': 'getAlertLogs', 'post': False}, + 'query_alerts': {'a': 'queryAlerts', 'post': False}, + 'status': {'post': False}, + 'reconfigure': {'post': True}, + 'restart': {'post': True}, + 'start': {'post': True}, + 'stop': {'post': True}, + 'drop_alert_log': {'a': 'dropAlertLog', 'post': True}, + 'reload_rules': {'a': 'reloadRules', 'post': True}, + 'update_rules': {'a': 'updateRules', 'post': True}, +} + +def run_module(): + module_args = dict( + action=dict( + type='str', required=True, aliases=['do', 'a'], + choices=[ + 'get_alert_logs', 'query_alerts', 'get_alert_info', 'status', + 'reconfigure', 'restart', 'start', 'stop', + 'drop_alert_log', 'reload_rules', 'update_rules', + ], + ), + alert_id=dict( + type='str', required=False, aliases=['alert'], + description="Parameter Alert-ID needed for 'get_alert_info'", + ), + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + action = module.params['action'] + if action == 'get_alert_info' and module.params['alert_id'] is None: + module.fail_json("You need to provide an Alert-ID as 'alert_id' to execute 'get_alert_info'!") + + # translate actions to api-commands + cmd = action + if 'a' in ACTION_MAPPING[action]: + cmd = ACTION_MAPPING[action]['a'] + + result['executed'] = cmd + + # execute action or pull status + if ACTION_MAPPING[action]['post']: + result['changed'] = True + + if not module.check_mode: + single_post( + module=module, + cnf={ + 'module': 'ids', + 'controller': 'service', + 'command': cmd, + } + ) + + else: + params = [] + if module.params['alert_id'] is not None: + params = [module.params['alert_id']] + + info = single_get( + module=module, + cnf={ + 'module': 'ids', + 'controller': 'service', + 'command': cmd, + 'params': params, + } + ) + + if 'response' in info: + info = info['response'] + + if isinstance(info, str): + info = info.strip() + + result['data'] = info + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_general.py new file mode 100644 index 0000000..1df2b3f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_general.py @@ -0,0 +1,144 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ids.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ids.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ids.html' + + +def run_module(): + module_args = dict( + interfaces=dict( + type='list', elements='str', required=True, aliases=['ints'], + description='Select interface(s) to use. When enabling IPS, only use physical interfaces here ' + '(no vlans etc)' + ), + block=dict( + type='bool', required=False, default=False, aliases=['protection', 'ips'], + description='Enable protection mode (block traffic). ' + 'Before enabling, please disable all hardware offloading first in advanced network!' + ), + promiscuous=dict( + type='bool', required=False, default=False, aliases=['physical', 'vlan'], + description='For certain setups (like IPS with vlans), this is required to actually capture data ' + 'on the physical interface' + ), + syslog_alerts=dict( + type='bool', required=False, default=False, aliases=['syslog', 'log'], + description='Send alerts to system log in fast log format. This will not change the alert ' + 'logging used by the product itself' + ), + syslog_output=dict( + type='bool', required=False, default=False, aliases=['log_stdout'], + description='Send alerts in eve format to syslog, using log level info. This will not change the alert ' + 'logging used by the product itself. Drop logs will only be send to the internal logger, ' + 'due to restrictions in suricata' + ), + log_payload=dict( + type='bool', required=False, default=False, aliases=['log_packet'], + description='Send packet payload to the log for further analyses' + ), + default_packet_size=dict( + type='int', required=False, aliases=['packet_size'], + description='With this option, you can set the size of the packets on your network. It is possible that ' + 'bigger packets have to be processed sometimes. The engine can still process these bigger ' + 'packets, but processing it will lower the performance. Unset = system default' + ), + log_retention=dict( + type='int', required=False, default=4, aliases=['log_count'], + description='Number of logs to keep' + ), + local_networks=dict( + type='list', elements='str', required=False, aliases=['home_networks'], + default=['192.168.0.0/16', '10.0.0.0/8', '172.16.0.0/12'], + description='Networks to interpret as local' + ), + log_level=dict( + type='str', required=False, + choices=['info', 'perf', 'config', 'debug'], + description='Increase the verbosity of the Suricata application logging by increasing the log level ' + 'from the default. Unset = system default' + ), + pattern_matcher=dict( + type='str', required=False, aliases=['algorithm', 'matcher', 'algo'], + choices=['ac', 'ac-bs', 'ac-ks', 'hs'], + description="Select the multi-pattern matcher algorithm to use. Options: unset = system default, " + "'ac' = 'Aho-Corasick', 'ac-bs' = 'Aho-Corasick, reduced memory implementation', " + "'ac-ks' = 'Aho-Corasick, Ken Steele variant', 'hs' = 'Hyperscan'" + ), + log_rotate=dict( + type='str', required=False, default='weekly', + choices=['weekly', 'daily'], + description='Rotate alert logs at provided interval' + ), + profile=dict( + type='str', required=False, aliases=['detect_profile'], + choices=['low', 'medium', 'high', 'custom'], + description="The detection engine builds internal groups of signatures. The engine allow us to specify " + "the profile to use for them, to manage memory on an efficient way keeping a good performance. " + "Unset = system default" + ), + profile_toclient_groups=dict( + type='str', required=False, aliases=['toclient_groups'], + description='If Custom is specified. The detection engine tries to split out separate signatures into ' + 'groups so that a packet is only inspected against signatures that can actually match. ' + 'As in large rule set this would result in way too many groups and memory usage similar ' + 'groups are merged together' + ), + profile_toserver_groups=dict( + type='str', required=False, aliases=['toserver_groups'], + description='If Custom is specified. The detection engine tries to split out separate signatures into ' + 'groups so that a packet is only inspected against signatures that can actually match. ' + 'As in large rule set this would result in way too many groups and memory usage similar ' + 'groups are merged together' + ), + schedule=dict( + type='str', required=False, default='ids rule updates', aliases=['update_cron', 'cron'], + description='Name/Description of an existing cron-job that should be used to update IDS' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_policy.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_policy.py new file mode 100644 index 0000000..f631f00 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_policy.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ids.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_policy import Policy + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ids.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ids.html' + + +def run_module(): + module_args = dict( + description=dict(type='str', required=True, aliases=['name', 'desc']), + priority=dict( + type='int', required=False, aliases=['prio'], default=0, + description='Policies are processed on a first match basis a lower number means more important', + ), + rulesets=dict( + type='list', elements='str', required=False, aliases=['rs'], default=[], + description='Rulesets this policy applies to (all when none selected)', + ), + action=dict( + type='list', elements='str', required=False, aliases=['a'], + choices=['disable', 'alert', 'drop'], + description='Rule configured action', + ), + new_action=dict( + type='str', required=False, aliases=['na'], default='alert', + choices=['default', 'disable', 'alert', 'drop'], + description='Action to perform when filter policy applies', + ), + rules=dict( + type='dict', required=False, + description="Key-value pairs of policy-rules as provided by the enabled rulesets. " + "Values must be string or lists. Example: " + "'{\"rules\": {\"signature_severity\": [\"Minor\", \"Major\"], \"tag\": \"Dshield\"}}'", + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Policy(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_policy_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_policy_rule.py new file mode 100644 index 0000000..59fbbec --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_policy_rule.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ids.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_policy_rule import Rule + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ids.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ids.html' + + +def run_module(): + module_args = dict( + sid=dict( + type='int', required=True, aliases=['id'], + description='Unique signature-ID of the rule you want to match' + ), + action=dict( + type='str', required=False, aliases=['a'], default='alert', + choices=['alert', 'drop'], + description='Rule configured action', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Rule(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_rule.py new file mode 100644 index 0000000..307b4c2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_rule.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ids.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_rule import Rule + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ids.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ids.html' + + +def run_module(): + module_args = dict( + sid=dict( + type='int', required=True, aliases=['id'], + description='Unique signature-ID of the rule you want to modify' + ), + action=dict( + type='str', required=False, aliases=['a'], default='alert', + choices=['alert', 'drop'], + description='Set action to perform here, only used when in IPS mode', + ), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Rule(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_ruleset.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_ruleset.py new file mode 100644 index 0000000..15eede0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_ruleset.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ids.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_ruleset import Ruleset + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ids.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ids.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['name', 'desc'], + description='Name of the ruleset you want to modify' + ), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Ruleset(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_user_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_user_rule.py new file mode 100644 index 0000000..e974442 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ids_user_rule.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ids.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_user_rule import Rule + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ids.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ids.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['name', 'desc'], + description='Unique rule name', + ), + source_ip=dict( + type='str', required=False, aliases=['source', 'src_ip', 'src'], + description="Set the source IP or network to match. Leave this field empty for using 'any'", + ), + destination_ip=dict( + type='str', required=False, aliases=['destination', 'dst_ip', 'dst'], + description="Set the destination IP or network to match. Leave this field empty for using 'any'", + ), + ssl_fingerprint=dict( + type='str', required=False, aliases=['fingerprint', 'ssl_fp'], + description="The SSL fingerprint, for example: " + "'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E'", + ), + action=dict( + type='str', required=False, aliases=['a'], default='alert', + choices=['alert', 'drop', 'pass'], + description='Set action to perform here, only used when in IPS mode', + ), + bypass=dict( + type='bool', required=False, aliases=['bp'], default=False, + description='Set bypass keyword. Increases traffic throughput. Suricata reads a packet, ' + 'decodes it, checks it in the flow table. If the corresponding flow is local ' + 'bypassed then it simply skips all streaming, detection and output and the packet ' + 'goes directly out in IDS mode and to verdict in IPS mode', + ), + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Rule(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_bridge.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_bridge.py new file mode 100644 index 0000000..7f74900 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_bridge.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_bridge import Bridge + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc'], + description='The unique description used to match the configured entries to the existing ones.', + ), + members=dict( + type='list', elements='str', required=False, aliases=['ports', 'ints'], + description='Interfaces participating in the bridge. - you must provide the network ' + "port as shown in 'Interfaces - Assignments - Network port'", + ), + link_local=dict( + type='bool', required=False, default=False, + description='Enable link-local addresses on the interface.', + ), + stp=dict( + type='bool', required=False, default=False, + description='Enable spanning tree options for this bridge.', + ), + stp_proto=dict( + type='str', required=False, aliases=['desc'], default='rstp', choices=['rstp', 'stp'], + description='Protocol used for spanning tree.', + ), + stp_interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['stp_ports', 'stp_ints'], + description='Interfaces to enable Spanning Tree Protocol on.', + ), + stp_max_age=dict( + type='int', required=False, + description='Time that a Spanning Tree Protocol configuration is valid.', + ), + stp_fwdelay=dict( + type='int', required=False, + description='Time that must pass before an interface begins forwarding packets.', + ), + stp_hold=dict( + type='int', required=False, + description='Tansmit hold count for Spanning Tree.', + ), + cache_size=dict( + type='int', required=False, + description='Size of the bridge address cache.', + ), + cache_timeout=dict( + type='int', required=False, + description='Timeout of address cache entries.', + ), + span_interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['span_ports', 'span_ints'], + description='Interfaces to add as span ports.', + ), + edge_interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['edge_ports', 'edge_ints'], + description='Interfaces to set as edge ports.', + ), + auto_edge_interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['auto_edge_ports', 'auto_edge_ints'], + description='Allow selected interfaces to automatically detect edge status.', + ), + ptp_interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['ptp_ports', 'ptp_ints'], + description='Interfaces to set as point-to-point link.', + ), + auto_ptp_interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['edge_ports', 'edge_ints'], + description='Automatically detect the point-to-point status on selected interfaces.', + ), + static_interfaces=dict( + type='list', elements='str', required=False, default=[], + aliases=['static_ports', 'static_ints', 'sticky_interfaces', 'sticky_ports', 'sticky_ints'], + description='Mark interfaces as a "sticky" interface.', + ), + private_interfaces=dict( + type='list', elements='str', required=False, default=[], aliases=['private_ports', 'eprivate_ints'], + description='Mark interfaces as a "private" interface.', + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('members',)), + ], + ) + + module_wrapper(Bridge(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_gif.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_gif.py new file mode 100644 index 0000000..178ee31 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_gif.py @@ -0,0 +1,90 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_gif import Gif + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc'], + description='The unique description used to match the configured entries to the existing ones.', + ), + local=dict( + type='str', required=False, aliases=['l', 'local_addr'], + description='The local address or interface to use.', + ), + remote=dict( + type='str', required=False, aliases=['r', 'remote_addr'], + description='Peer address where encapsulated gif packets will be sent.', + ), + tunnel_local=dict( + type='str', required=False, aliases=['tl', 'tunnel_local_addr'], + description='Local gif tunnel endpoint.', + ), + tunnel_remote=dict( + type='str', required=False, aliases=['tr', 'tunnel_remote_addr'], + description='Remote gif tunnel endpoint.', + ), + tunnel_remote_net=dict( + type='int', required=False, default=32, + description="Netmask 'ipv4' or prefix 'ipv6' to use for this tunnel", + ), + ingress_filtering=dict( + type='bool', required=False, default=True, aliases=['filtering'], + description='Enable ingress filtering on outer tunnel source tunnel', + ), + ecn_friendly=dict( + type='bool', required=False, default=False, aliases=['ecn'], + description='Enable ECN friendly behavior this violates RFC2893', + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('local', 'remote', 'tunnel_local', 'tunnel_remote')), + ], + ) + + module_wrapper(Gif(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_gre.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_gre.py new file mode 100644 index 0000000..1665d6c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_gre.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_gre import Gre + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc'], + description='The unique description used to match the configured entries to the existing ones.', + ), + local=dict( + type='str', required=False, aliases=['l', 'local_addr'], + description='The local address or interface to use.', + ), + remote=dict( + type='str', required=False, aliases=['r', 'remote_addr'], + description='Peer address where encapsulated gre packets will be sent.', + ), + tunnel_local=dict( + type='str', required=False, aliases=['tl', 'tunnel_local_addr'], + description='Local gre tunnel endpoint.', + ), + tunnel_remote=dict( + type='str', required=False, aliases=['tr', 'tunnel_remote_addr'], + description='Remote gre tunnel endpoint.', + ), + tunnel_remote_net=dict( + type='int', required=False, default=32, + description="Netmask 'ipv4' or prefix 'ipv6' to use for this tunnel", + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('local', 'remote', 'tunnel_local', 'tunnel_remote')), + ], + ) + + module_wrapper(Gre(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_lagg.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_lagg.py new file mode 100644 index 0000000..c500cc3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_lagg.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/interfaces.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_lagg import Lagg + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface.html' + + +def run_module(): + module_args = dict( + device=dict( + type='str', required=False, aliases=['laggif'], + description="Optional 'device' of the entry. Needs to start with 'lagg'", + ), + members=dict( + type='list', elements='str', required=False, aliases=['port', 'int', 'if', 'parent'], + description='Existing LAGG capable interface - you must provide the network ' + "port as shown in 'Interfaces - Assignments - Network port'" + ), + primary_member=dict( + type='list', elements='str', required=False, + description='This interface will be added first in the lagg making it the primary one ' + "- you must provide the network port as shown in 'Interfaces - Assignments - Network port'" + ), + proto=dict( + type='str', required=False, aliases=['p'], default='lacp', + choices=['none', 'lacp', 'failover', 'fec', 'loadbalance', 'roundrobin'], + description="The protocol to use." + ), + lacp_fast_timeout=dict(type='bool', required=False, default=False, aliases=['fast_timeout'], + description='Enable lacp fast-timeout on the interface.' + ), + use_flowid=dict( + type='str', required=False, choices=['yes', 'no'], aliases=['flowid'], + description='Use the RSS hash from the network card if available, otherwise a hash is locally calculated. ' + 'The default depends on the system tunable in net.link.lagg.default_use_flowid.' + ), + lagghash=dict( + type='list', elements='str', required=False, aliases=['hash', 'hash_layers'], + choices=['l2', 'l3', 'l4'], + description='Set the packet layers to hash for aggregation protocols which load balance.' + ), + lacp_strict=dict( + type='str', required=False, + choices=['yes', 'no'], + description='Enable lacp strict compliance on the interface. The default depends on the ' + 'system tunable in net.link.lagg.lacp.default_strict_mode.', + ), + mtu=dict( + type='int', required=False, + description='If you leave this field blank, the smallest mtu of this laggs children will be used.' + ), + description=dict(type='str', required=False, aliases=['desc', 'name']), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Lagg(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_loopback.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_loopback.py new file mode 100644 index 0000000..5ba9280 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_loopback.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# template to be copied to implement new modules + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_loopback import Loopback + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface_loopback.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface_loopback.html' + + +def run_module(): + module_args = dict( + description=dict(type='str', required=True, aliases=['desc']), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Loopback(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vip.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vip.py new file mode 100644 index 0000000..b054609 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vip.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/interfaces.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vip import Vip + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface.html' + + +def run_module(): + module_args = dict( + address=dict( + type='str', required=True, aliases=['addr', 'ip', 'network', 'net'], + description='Provide an address and subnet to use. (e.g 192.168.0.1/24)', + ), + interface=dict( + type='str', required=True, aliases=['port', 'int', 'if'], + description='Existing interface - you must provide the network ' + "port as shown in 'Interfaces - Assignments - Network port'" + ), + mode=dict( + type='str', required=False, aliases=['m'], default='ipalias', + choices=['ipalias', 'carp', 'proxyarp', 'other'], + ), + expand=dict(type='bool', required=False, default=True), + bind=dict( + type='bool', required=False, default=True, + description="Assigning services to the virtual IP's interface will automatically " + "include this address. Uncheck to prevent binding to this address instead" + ), + gateway=dict( + type='str', required=False, aliases=['gw'], + description='For some interface types a gateway is required to configure an ' + 'IP Alias (ppp/pppoe/tun), leave this field empty for all other interface types' + ), + peer=dict( + type='str', required=False, + description='Destination address to use when announcing, defaults to multicast, ' + 'but can be configured as unicast address when multicast can not be ' + 'used (for example with cloud providers)' + ), + peer6=dict( + type='str', required=False, + description='Destination address to use when announcing, defaults to multicast, ' + 'but can be configured as unicast address when multicast can not be ' + 'used (for example with cloud providers)' + ), + password=dict( + type='str', required=False, aliases=['pwd'], + description='VHID group password', no_log=True, + ), + vhid=dict( + type='str', required=False, aliases=['group', 'grp', 'id'], + description='VHID group that the machines will share' + ), + advertising_base=dict( + type='int', required=False, aliases=['adv_base', 'base'], default=1, + description='The frequency that this machine will advertise. 0 usually means master. ' + 'Otherwise the lowest combination of both values in the cluster determines the master' + ), + advertising_skew=dict( + type='int', required=False, aliases=['adv_skew', 'skew'], default=0, + ), + description=dict(type='str', required=False, aliases=['desc']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured VIP with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['address', 'interface', 'cidr', 'description'], + default=['address', 'interface'], + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Vip(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vlan.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vlan.py new file mode 100644 index 0000000..4ec3a0b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vlan.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/interfaces.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vlan import Vlan + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface.html' + + +def run_module(): + module_args = dict( + device=dict( + type='str', required=False, aliases=['vlanif'], + description="Optional 'device' of the entry. Needs to start with 'vlan0'", + ), + interface=dict( + type='str', required=False, aliases=['parent', 'port', 'int', 'if'], + description='Existing VLAN capable interface - you must provide the network ' + "port as shown in 'Interfaces - Assignments - Network port'" + ), + vlan=dict( + type='int', required=False, aliases=['tag', 'id'], + description='802.1Q VLAN tag (between 1 and 4094)' + ), + priority=dict( + type='int', required=False, default=0, aliases=['prio', 'pcp'], + description='802.1Q VLAN PCP (priority code point)' + ), + description=dict(type='str', required=True, aliases=['desc', 'name']), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Vlan(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vxlan.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vxlan.py new file mode 100644 index 0000000..546a27d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/interface_vxlan.py @@ -0,0 +1,94 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/interfaces.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vxlan import Vxlan + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface.html' + + +def run_module(): + module_args = dict( + # device_id=dict(type='str', required=True), # can't be configured + interface=dict(type='str', required=False, aliases=['vxlandev', 'device', 'int']), + id=dict(type='int', required=True, aliases=['vxlanid', 'vni']), + local=dict( + type='str', required=False, aliases=[ + 'source_address', 'source_ip', 'vxlanlocal', 'source', 'src', + ], + description='The source address used in the encapsulating IPv4/IPv6 header. The address should ' + 'already be assigned to an existing interface. When the interface is configured in ' + 'unicast mode, the listening socket is bound to this address.' + ), + local_port=dict( + type='int', required=False, aliases=[ + 'source_port', 'vxlanlocalport', 'srcport', + ], + description='Define the port to be used.' + ), + remote=dict( + type='str', required=False, aliases=[ + 'remote_address', 'remote_ip', 'destination', 'vxlanremote', 'dest', + ], + description='The interface can be configured in a unicast, or point-to-point, mode to create ' + 'a tunnel between two hosts. This is the IP address of the remote end of the tunnel.' + ), + remote_port=dict( + type='int', required=False, aliases=[ + 'destination_port', 'vxlanremoteport', 'destport', + ], + description='Define the port to be used.' + ), + group=dict( + type='str', required=False, aliases=[ + 'multicast_group', 'multicast_address', 'multicast_ip', 'vxlangroup', + ], + description='The interface can be configured in a multicast mode to create a virtual ' + 'network of hosts. This is the IP multicast group address the interface will join.' + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Vxlan(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_auth_local.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_auth_local.py new file mode 100644 index 0000000..eca321b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_auth_local.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.ipsec_auth import \ + IPSEC_AUTH_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_auth_local import \ + Auth + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + module_args = dict( + **IPSEC_AUTH_MOD_ARGS, + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Auth(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_auth_remote.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_auth_remote.py new file mode 100644 index 0000000..cc02b03 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_auth_remote.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.ipsec_auth import \ + IPSEC_AUTH_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_auth_remote import \ + Auth + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + module_args = dict( + **IPSEC_AUTH_MOD_ARGS, + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Auth(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_cert.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_cert.py new file mode 100644 index 0000000..02245fe --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_cert.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ipsec.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_cert import KeyPair + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG_DEF_FALSE + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + public_key=dict(type='str', required=False, aliases=['pub_key', 'pub']), + private_key=dict(type='str', required=False, aliases=['priv_key', 'priv'], no_log=True), + type=dict(type='str', required=False, choices=['rsa', 'ecdsa'], default='rsa'), + **RELOAD_MOD_ARG_DEF_FALSE, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(KeyPair(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_child.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_child.py new file mode 100644 index 0000000..bcab5e6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_child.py @@ -0,0 +1,126 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_child import \ + Child + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['description', 'desc'], + description='Unique name to identify the entry' + ), + connection=dict( + type='str', required=False, aliases=['tunnel', 'conn', 'tun'], + description='Connection to link this child to' + ), + mode=dict( + type='str', required=False, default='tunnel', + choices=['tunnel', 'transport', 'pass', 'drop'], + description='IPsec Mode to establish CHILD_SA with. tunnel negotiates the CHILD_SA in IPsec Tunnel ' + 'Mode whereas transport uses IPsec Transport Mode. pass and drop are used to install ' + 'shunt policies which explicitly bypass the defined traffic from IPsec processing or ' + 'drop it, respectively', + ), + request_id=dict( + type='str', required=False, aliases=['req_id', 'reqid'], + description='This might be helpful in some scenarios, like route based tunnels (VTI), but works only if ' + 'each CHILD_SA configuration is instantiated not more than once. The default uses dynamic ' + 'reqids, allocated incrementally', + ), + esp_proposals=dict( + type='list', elements='str', required=False, default=['default'], + aliases=['esp_props', 'esp'], + ), + sha256_96=dict( + type='bool', required=False, default=False, aliases=['sha256'], + description='HMAC-SHA-256 is used with 128-bit truncation with IPsec. For compatibility with ' + 'implementations that incorrectly use 96-bit truncation this option may be enabled to ' + 'configure the shorter truncation length in the kernel. This is not negotiated, so this ' + 'only works with peers that use the incorrect truncation length (or have this option enabled)', + ), + start_action=dict( + type='str', required=False, aliases=['start'], default='start', + choices=['none', 'trap|start', 'route', 'start', 'trap'], + description='Action to perform after loading the configuration. The default of none loads the connection ' + 'only, which then can be manually initiated or used as a responder configuration. The value ' + 'trap installs a trap policy which triggers the tunnel as soon as matching traffic has been ' + 'detected. The value start initiates the connection actively. To immediately initiate a ' + 'connection for which trap policies have been installed, user Trap|start', + ), + close_action=dict( + type='str', required=False, aliases=['close'], default='none', + choices=['none', 'trap', 'start'], + ), + dpd_action=dict( + type='str', required=False, aliases=['dpd'], default='clear', + choices=['clear', 'trap', 'start'], + ), + policies=dict( + type='bool', required=False, default=True, aliases=['pols'], + description='Whether to install IPsec policies or not. Disabling this can be useful in some scenarios ' + 'e.g. VTI where policies are not managed by the IKE daemon', + ), + local_net=dict( + type='list', elements='str', required=False, default=[], + aliases=['local_traffic_selectors', 'local_cidr', 'local_ts', 'local'], + description='List of local traffic selectors to include in CHILD_SA. Each selector is a CIDR ' + 'subnet definition', + ), + remote_net=dict( + type='list', elements='str', required=False, default=[], + aliases=['remote_traffic_selectors', 'remote_cidr', 'remote_ts', 'remote'], + description='List of remote traffic selectors to include in CHILD_SA. Each selector is a CIDR ' + 'subnet definition', + ), + rekey_seconds=dict( + type='int', default=3600, required=False, aliases=['rekey_time', 'rekey'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Child(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_connection.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_connection.py new file mode 100644 index 0000000..1ae6f1d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_connection.py @@ -0,0 +1,211 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_connection import \ + Connection + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['description', 'desc'], + description='Unique connection/tunnel name', + ), + local_addresses=dict( + type='list', elements='str', required=False, aliases=['local_addr', 'local'], default=[], + description='Local address[es] to use for IKE communication. Accepts single IPv4/IPv6 addresses, ' + 'DNS names, CIDR subnets or IP address ranges. As an initiator, the first non-range/non-subnet ' + 'is used to initiate the connection from. As a responder the local destination address must ' + 'match at least to one of the specified addresses, subnets or ranges. If FQDNs are assigned, ' + 'they are resolved every time a configuration lookup is done. If DNS resolution times out, ' + 'the lookup is delayed for that time. When left empty %any is choosen as default', + ), + local_port=dict( + type='str', required=False, default='500', options=['500', '4500'], + description='UDP port for IKE communication. If the default of port 500 is used, automatic IKE ' + 'port floating to port 4500 is used to work around NAT issues.', + ), + remote_addresses=dict( + type='list', elements='str', required=False, aliases=['remote_addr', 'remote'], default=[], + description='Remote address[es] to use for IKE communication. Accepts single IPv4/IPv6 addresses, ' + 'DNS names, CIDR subnets or IP address ranges. As an initiator, the first non-range/non-subnet ' + 'is used to initiate the connection to. As a responder, the initiator source address must ' + 'match at least to one of the specified addresses, subnets or ranges. If FQDNs are assigned ' + 'they are resolved every time a configuration lookup is done. If DNS resolution times out, ' + 'the lookup is delayed for that time. To initiate a connection, at least one specific address ' + 'or DNS name must be specified', + ), + remote_port=dict( + type='str', required=False, default='500', options=['500', '4500'], + description='UDP port for IKE communication. If the default of port 500 is used, automatic IKE ' + 'port floating to port 4500 is used to work around NAT issues.', + ), + pools=dict( + type='list', elements='str', required=False, default=[], aliases=['networks', 'nets'], + description='List of named IP pools to allocate virtual IP addresses and other configuration attributes ' + 'from. Each name references a pool by name from either the pools section or an external pool. ' + 'Note that the order in which they are queried primarily depends on the plugin order', + ), + proposals=dict( + type='list', elements='str', required=False, default=['default'], aliases=['props'], + description='A proposal is a set of algorithms. For non-AEAD algorithms this includes IKE an encryption ' + 'algorithm, an integrity algorithm, a pseudo random function (PRF) and a Diffie-Hellman key ' + 'exchange group. For AEAD algorithms, instead of encryption and integrity algorithms a ' + 'combined algorithm is used. With IKEv2 multiple algorithms of the same kind can be specified ' + 'in a single proposal, from which one gets selected. For IKEv1 only one algorithm per kind ' + 'is allowed per proposal, more algorithms get implicitly stripped. Use multiple proposals ' + 'to offer different algorithm combinations with IKEv1. Algorithm keywords get separated ' + 'using dashes. Multiple proposals may be separated by commas. The special value default adds ' + 'a default proposal of supported algorithms considered safe and is usually a good choice ' + 'for interoperability', + ), + unique=dict( + type='str', required=False, default='no', + choices=['no', 'never', 'keep', 'replace'], + description='Connection uniqueness policy to enforce. To avoid multiple connections from the same user, ' + 'a uniqueness policy can be enforced', + ), + aggressive=dict( + type='bool', required=False, default=False, aliases=['aggr'], + description='Enables IKEv1 Aggressive Mode instead of IKEv1 Main Mode with Identity Protection. ' + 'Aggressive Mode is considered less secure because the ID and HASH payloads are exchanged ' + 'unprotected. This allows a passive attacker to snoop peer identities and even worse, ' + 'start dictionary attacks on the Preshared Key', + ), + version=dict( + type='str', required=False, default='ikev1+2', aliases=['vers', 'v'], + choices=list(Connection.FIELDS_VALUE_MAPPING['version'].keys()), + description='IKE major version to use for connection. 1 uses IKEv1 aka ISAKMP, 2 uses IKEv2. A connection ' + 'using IKEv1+IKEv2 accepts both IKEv1 and IKEv2 as a responder and initiates the connection ' + 'actively with IKEv2', + ), + mobike=dict( + type='bool', required=False, default=True, aliases=['mob'], + description='Enables MOBIKE on IKEv2 connections. MOBIKE is enabled by default on IKEv2 connections and ' + 'allows mobility of clients and multi-homing on servers by migrating active IPsec tunnels. ' + 'Usually keeping MOBIKE enabled is unproblematic, as it is not used if the peer does not ' + 'indicate support for it. However, due to the design of MOBIKE, IKEv2 always floats to UDP ' + 'port 4500 starting from the second exchange. Some implementations don’t like this behavior, ' + 'hence it can be disabled', + ), + encapsulation=dict( + type='bool', required=False, default=False, aliases=['udp_encapsulation', 'encap'], + description='To enforce UDP encapsulation of ESP packets, the IKE daemon can manipulate the NAT detection ' + 'payloads. This makes the peer believe that a NAT situation exist on the transmission path, ' + 'forcing it to encapsulate ESP packets in UDP. Usually this is not required but it can help ' + 'to work around connectivity issues with too restrictive intermediary firewalls that block ' + 'ESP packets', + ), + reauth_seconds=dict( + type='int', required=False, aliases=['reauth', 'reauth_sec', 'reauth_time'], + description='Time to schedule IKE reauthentication. IKE reauthentication recreates the IKE/ISAKMP SA ' + 'from scratch and re-evaluates the credentials. In asymmetric configurations (with EAP or ' + 'configuration payloads) it might not be possible to actively reauthenticate as responder. ' + 'The IKEv2 reauthentication lifetime negotiation can instruct the client to perform ' + 'reauthentication. Reauthentication is disabled by default (0). Enabling it usually may ' + 'lead to small connection interruptions as strongSwan uses a break-before-make policy ' + 'with IKEv2 by default', + ), + rekey_seconds=dict( + type='int', required=False, aliases=['rekey', 'rekey_sec', 'rekey_time'], + description='IKE rekeying refreshes key material using a Diffie-Hellman key exchange, but does not ' + 're-check associated credentials. It is supported with IKEv2 only. IKEv1 performs a ' + 'reauthentication procedure instead. With the default value, IKE rekeying is scheduled ' + 'every 4 hours minus the configured rand_time. If a reauth_time is configured, rekey_time ' + 'defaults to zero, disabling rekeying. In that case set rekey_time explicitly to both ' + 'enforce rekeying and reauthentication', + ), + over_seconds=dict( + type='int', required=False, aliases=['over', 'over_sec', 'over_time'], + description='Hard IKE_SA lifetime if rekey/reauth does not complete, as time. To avoid having an IKE or ' + 'ISAKMP connection kept alive if IKE reauthentication or rekeying fails perpetually, a ' + 'maximum hard lifetime may be specified. If the IKE_SA fails to rekey or reauthenticate ' + 'within the specified time, the IKE_SA gets closed. In contrast to CHILD_SA rekeying, ' + 'over_time is relative in time to the rekey_time and reauth_time values, as it applies ' + 'to both. The default is 10% of either rekey_time or reauth_time, whichever value is larger. ' + '[0.1 * max(rekey_time, reauth_time)]', + ), + dpd_delay_seconds=dict( + type='int', required=False, aliases=['dpd_delay', 'dpd_delay_sec', 'dpd_delay_time'], + description='Interval to check the liveness of a peer actively using IKEv2 INFORMATIONAL exchanges or ' + 'IKEv1 R_U_THERE messages. Active DPD checking is only enforced if no IKE or ESP/AH packet ' + 'has been received for the configured DPD delay. Defaults to 0s', + ), + dpd_timeout_seconds=dict( + type='int', required=False, aliases=['dpd_timeout', 'dpd_timeout_sec'], + description='Charon by default uses the normal retransmission mechanism and timeouts to check the ' + 'liveness of a peer, as all messages are used for liveness checking. For compatibility ' + 'reasons, with IKEv1 a custom interval may be specified. This option has no effect on ' + 'IKEv2 connections', + ), + send_certificate_request=dict( + type='bool', required=False, default=True, aliases=['send_cert_req'], + description='Send certificate request payloads to offer trusted root CA certificates to the peer. ' + 'Certificate requests help the peer to choose an appropriate certificate/private key for ' + 'authentication and are enabled by default. Disabling certificate requests can be useful ' + 'if too many trusted root CA certificates are installed, as each certificate request ' + 'increases the size of the initial IKE packets', + ), + send_certificate=dict( + type='str', required=False, aliases=['send_cert'], + choices=['ifasked', 'never', 'always'], + description='Send certificate payloads when using certificate authentication. With the default of ' + '[ifasked] the daemon sends certificate payloads only if certificate requests have been ' + 'received. [never] disables sending of certificate payloads altogether whereas [always] ' + 'causes certificate payloads to be sent unconditionally whenever certificate-based ' + 'authentication is used', + ), + keying_tries=dict( + type='int', required=False, aliases=['keyingtries'], + description='Number of retransmission sequences to perform during initial connect. Instead of giving ' + 'up initiation after the first retransmission sequence with the default value of 1, ' + 'additional sequences may be started according to the configured value. A value of 0 ' + 'initiates a new sequence until the connection establishes or fails with a permanent error', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Connection(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_general.py new file mode 100644 index 0000000..9c9a873 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_general.py @@ -0,0 +1,280 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + prefer_old_sa=dict( + type='bool', required=False, default=False, + description='If several SAs match always prefer old SAs over new ones', + ), + disable_vpn_rules=dict( + type='bool', required=False, default=False, + description='This option only applies to legacy tunnel configurations, connections do require manual ' + 'firewall rules being setup', + ), + passthrough_networks=dict( + type='list', elements='str', required=False, default=[], + description='Exempts traffic for one or more subnets from getting processed by the IPsec stack in the kernel', + ), + authentication=dict( + type='list', elements='str', required=False, default=[], + description='Select authentication methods to use, leave empty if no challenge response authentication ' + 'is needed', + ), + local_group=dict( + type='str', required=False, + description='Restrict access to users in the selected local group', + ), + # eap-radius + radius_servers=dict( + type='list', elements='str', required=False, default=[], + description='RADIUS servers to configure', + ), + radius_accounting=dict( + type='bool', required=False, default=False, + description='Enable RADIUS accounting', + ), + radius_class_group=dict( + type='bool', required=False, default=False, + description='Enable RADIUS Group selection (class_group)', + ), + # xauth-pam + pam_service=dict( + type='str', required=False, default='ipsec', + description='PAM service to use for authentication', + ), + pam_session=dict( + type='bool', required=False, default=False, + description='Open/close a PAM session for each active IKE_SA', + ), + pam_trim_email=dict( + type='bool', required=False, default=True, + description='If an email address is received as an XAuth username, trim it to just the username part', + ), + # Charon + charon_max_ikev1_exchanges=dict( + type='int', required=False, + description='Maximum number of IKEv1 phase 2 exchanges per IKE_SA to keep state about and track concurrently', + ), + charon_threads=dict( + type='int', required=False, default=16, + description='Number of worker threads, several of these are reserved for long running tasks ' + 'in internal modules and plugins', + ), + charon_ikesa_table_size=dict( + type='int', required=False, default=32, + description='Size of the IKE SA hash table', + ), + charon_ikesa_table_segments=dict( + type='int', required=False, default=4, + description='Number of exclusively locked segments in the hash table', + ), + charon_init_limit_half_open=dict( + type='int', required=False, default=1000, + description='Limit new connections based on the current number of half open IKE_SAs', + ), + charon_ignore_acquire_ts=dict( + type='bool', required=False, default=True, + description='Prefix each log entry with the connection name and a unique identifier for each IKE_SA', + ), + charon_make_before_break=dict( + type='bool', required=False, default=False, + description='Initiate IKEv2 reauthentication with a make-before-break instead of a break-before-make scheme', + ), + charon_install_routes=dict( + type='bool', required=False, default=False, + description='Install routes into a separate routing table for established IPsec tunnels', + ), + charon_cisco_unity=dict( + type='bool', required=False, default=False, + description='Send Cisco Unity vendor ID payload (IKEv1 only)', + ), + # Retransmission + retransmit_tries=dict( + type='int', required=False, + description='Number of retransmissions to send before giving up', + ), + retransmit_timeout=dict( + type='int', required=False, + description='Timeout in seconds', + ), + retransmit_base=dict( + type='int', required=False, + description='Base of exponential backoff', + ), + retransmit_jitter=dict( + type='int', required=False, + description='Maximum jitter in percent to apply randomly to calculated retransmission timeout (0 to disable)', + ), + retransmit_limit=dict( + type='int', required=False, + description='Upper limit in seconds for calculated retransmission timeout (0 to disable)', + ), + # Syslog + syslog_log_name=dict( + type='bool', required=False, default=True, + description='Prefix each log entry with the connection name and a unique numerical identifier ' + 'for each IKE_SA', + ), + syslog_log_level=dict( + type='bool', required=False, default=False, + description='Add the log level of each message after the subsystem (e.g. [IKE2])', + ), + syslog_app=dict( + type='int', required=False, default=1, + description='Log level for applications other than daemons', + ), + syslog_asn=dict( + type='int', required=False, default=1, + description='Log level for low-level encoding/decoding (ASN.1, X.509 etc.)', + ), + syslog_cfg=dict( + type='int', required=False, default=1, + description='Log level for configuration management and plugins', + ), + syslog_chd=dict( + type='int', required=False, default=1, + description='Log level for CHILD_SA/IPsec SA', + ), + syslog_dmn=dict( + type='int', required=False, default=1, + description='Log level for main daemon setup/cleanup/signal handling', + ), + syslog_enc=dict( + type='int', required=False, default=1, + description='Log level for packet encoding/decoding encryption/decryption operations', + ), + syslog_esp=dict( + type='int', required=False, default=1, + description='Log level for libipsec library messages', + ), + syslog_ike=dict( + type='int', required=False, default=1, + description='Log level for IKE_SA/ISAKMP SA', + ), + syslog_imc=dict( + type='int', required=False, default=1, + description='Log level for Integrity Measurement Collector', + ), + syslog_imv=dict( + type='int', required=False, default=1, + description='Log level for Integrity Measurement Verifier', + ), + syslog_job=dict( + type='int', required=False, default=1, + description='Log level for jobs queuing/processing and thread pool management', + ), + syslog_knl=dict( + type='int', required=False, default=1, + description='Log level for IPsec/Networking kernel interface', + ), + syslog_lib=dict( + type='int', required=False, default=1, + description='Log level for libstrongwan library messages', + ), + syslog_mgr=dict( + type='int', required=False, default=1, + description='Log level for IKE_SA manager, handling synchronization for IKE_SA access', + ), + syslog_net=dict( + type='int', required=False, default=1, + description='Log level for IKE network communication', + ), + syslog_pts=dict( + type='int', required=False, default=1, + description='Log level for Platform Trust Service', + ), + syslog_tls=dict( + type='int', required=False, default=1, + description='Log level for libtls library messages', + ), + syslog_tnc=dict( + type='int', required=False, default=1, + description='Log level for Trusted Network Connect', + ), + # Attr + attr_subnet=dict( + type='list', elements='str', required=False, default=[], + description='The protected sub-networks that this edge-device protects (in CIDR notation). ' + 'Usually ignored in deference to local_ts, though macOS clients will use this for routes', + ), + attr_dns=dict( + type='list', elements='str', required=False, default=[], + description='DNS server', + ), + attr_nbns=dict( + type='list', elements='str', required=False, default=[], aliases=['attr_wins'], + description='WINS server', + ), + # Cisco Unity + unity_split_include=dict( + type='list', elements='str', required=False, default=[], + description='Comma-separated list of subnets to tunnel. The unity plugin provides a connection specific ' + 'approach to assign this attribute', + ), + unity_dns_search=dict( + type='str', required=False, + description='Default search domain used when resolving host names via the assigned DNS servers', + ), + unity_dns_split=dict( + type='str', required=False, + description='If split tunneling is used clients might not install the assigned DNS servers globally. ' + 'This space-separated list of domain names allows clients, such as macOS, to selectively ' + 'query the assigned DNS servers', + ), + unity_login_banner=dict( + type='str', required=False, + description='Message displayed on certain clients after login', + ), + unity_save_password=dict( + type='bool', required=False, default=False, + description='Allow client to save Xauth password in local storage', + ), + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_manual_spd.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_manual_spd.py new file mode 100644 index 0000000..e3dd417 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_manual_spd.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_manual_spd import ManualSPD + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=False, aliases=['description', 'desc'], + description='Unique name to identify the entry', + ), + request_id=dict( + type='int', required=False, aliases=['req_id', 'reqid'], + description='Reqid to register this manual spd entry on.', + ), + connection_child=dict( + type='str', required=False, + description='Connection child to register this manual spd entry on.', + ), + source=dict( + type='str', required=False, aliases=['s', 'src', 'source_net'], + description='Source network, usually the networks you would like to accept using NAT.', + ), + destination=dict( + type='str', required=False, aliases=['d', 'dest', 'destination_net'], + description='Destination network, leave empty to use the networks propagated in the child sa.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('source',)), + ('state', 'present', ('request_id', 'connection_child'), True), + ], + mutually_exclusive=[ + ('request_id', 'connection_child'), + ], + ) + + module_wrapper(ManualSPD(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_pool.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_pool.py new file mode 100644 index 0000000..e1b828b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_pool.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_pool import \ + Pool + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, + description='Unique pool/network name' + ), + network=dict( + type='str', required=False, aliases=['net', 'cidr'], + description='Pool network in CIDR format', + ), + dns=dict( + type='list', elements='str', required=False, default=[], + description='DNS servers to push as configuration payload (RFC4306 3.15 - Value 3 and 10)', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Pool(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_psk.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_psk.py new file mode 100644 index 0000000..e5d9a4c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_psk.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/ipsec.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_psk import PreSharedKey + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG_DEF_FALSE + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + module_args = dict( + identity_local=dict( + type='str', required=True, aliases=['identity', 'ident'], + description='This can be either an IP address, fully qualified domain name or an email address.' + ), + identity_remote=dict( + type='str', required=False, aliases=['remote_ident'], + description='(optional) This can be either an IP address, fully qualified domain name or ' + 'an email address to identify the remote host.' + ), + psk=dict(type='str', required=False, no_log=True, aliases=['key', 'secret']), + type=dict( + type='str', required=False, choices=['PSK', 'EAP'], default='PSK', aliases=['kind'], + ), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured PSK with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['identity_local', 'identity_remote'], + default=['identity_local'], + ), + **RELOAD_MOD_ARG_DEF_FALSE, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(PreSharedKey(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_vti.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_vti.py new file mode 100644 index 0000000..5c25de2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/ipsec_vti.py @@ -0,0 +1,88 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_vti import \ + Vti + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/ipsec.html' + + +def run_module(): + # todo: add description to parameters => VTI not found in WebUI (?!) + module_args = dict( + name=dict( + type='str', required=True, aliases=['description', 'desc'], + description='Unique name to identify the entry' + ), + request_id=dict( + type='int', default=0, required=False, aliases=['req_id', 'reqid'], + description='This might be helpful in some scenarios, like route based tunnels (VTI), but works only if ' + 'each CHILD_SA configuration is instantiated not more than once. The default uses dynamic ' + 'reqids, allocated incrementally', + ), + local_address=dict( + type='str', required=False, aliases=['local_addr', 'local'], + ), + remote_address=dict( + type='str', required=False, aliases=['remote_addr', 'remote'], + ), + local_tunnel_address=dict( + type='str', required=False, aliases=['local_tun_addr', 'tunnel_local', 'local_tun'], + ), + remote_tunnel_address=dict( + type='str', required=False, aliases=['remote_tun_addr', 'tunnel_remote', 'remote_tun'], + ), + local_tunnel_secondary_address=dict( + type='str', required=False, aliases=['local_tun_sec_addr', 'tunnel_sec_local', 'local_sec_tun'], + ), + remote_tunnel_secondary_address=dict( + type='str', required=False, aliases=['remote_tun_sec_addr', 'tunnel_sec_remote', 'remote_sec_tun'], + ), + skip_firewall=dict( + type='bool', require=False, aliases=['skip_fw'], default=False + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Vti(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/list.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/list.py new file mode 100644 index 0000000..e5f4541 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/list.py @@ -0,0 +1,687 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# module to query running config +# pylint: disable=R0912,R0915,R0914 + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import OPN_MOD_ARGS + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/general/list.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/general/list.html' + +TARGETS = [ + 'alias', 'rule', 'rule_interface_group', 'route', 'gateway', 'syslog', 'package', 'unbound_host', + 'frr_ospf_general', 'frr_ospf3_general', 'unbound_forward', 'shaper_pipe', 'shaper_queue', 'shaper_rule', + 'monit_service', 'monit_test', 'monit_alert', 'wireguard_server', 'bind_domain', 'wireguard_peer', 'interface_vlan', + 'unbound_host_alias', 'interface_vxlan', 'frr_bfd_neighbor', 'frr_bgp_general', 'frr_bgp_neighbor', + 'frr_ospf3_interface', 'frr_ospf_interface', 'bind_acl', 'frr_ospf_network', 'frr_rip', 'bind_general', + 'bind_blocklist', 'bind_record', 'interface_vip', 'webproxy_general', 'webproxy_cache', 'webproxy_parent', + 'webproxy_traffic', 'webproxy_remote_acl', 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule', + 'cron', 'unbound_dot', 'ipsec_cert', 'ipsec_psk', 'source_nat', 'frr_bgp_prefix_list', 'frr_bgp_community_list', + 'frr_bgp_as_path', 'frr_bgp_route_map', 'frr_ospf_prefix_list', 'frr_ospf_route_map', 'webproxy_forward', + 'webproxy_acl', 'webproxy_icap', 'webproxy_auth', 'nginx_upstream_server', 'ipsec_connection', 'ipsec_pool', + 'ipsec_child', 'ipsec_vti', 'ipsec_auth_local', 'ipsec_auth_remote', 'frr_general', 'unbound_general', + 'unbound_acl', 'ids_general', 'ids_policy', 'ids_rule', 'ids_ruleset', 'ids_user_rule', 'ids_policy_rule', + 'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override', 'dhcrelay_destination', 'dhcrelay_relay', + 'interface_lagg', 'interface_loopback', 'unbound_dnsbl', 'dhcp_reservation', 'acme_general', 'acme_account', + 'acme_validation', 'acme_action', 'acme_certificate', 'postfix_general', 'postfix_domain', 'postfix_recipient', + 'postfix_recipientbcc', 'postfix_sender', 'postfix_senderbcc', 'postfix_sendercanonical', 'postfix_headercheck', + 'postfix_address', 'dhcp_subnet', 'dhcp_general', 'interface_gre', 'nat_one_to_one', 'nat_source', + 'ipsec_manual_spd', 'hasync_general', 'snapshot', 'frr_bgp_redistribution', 'frr_ospf_redistribution', + 'frr_ospf3_redistribution', 'frr_ospf3_route_map', 'frr_ospf3_prefix_list', 'frr_ospf3_network', + 'frr_bgp_peer_group', 'user', 'group', 'privilege', 'interface_bridge', 'interface_gif', 'neighbor', + 'dnsmasq_general', 'ipsec_general', 'dnsmasq_domain', 'dnsmasq_host', 'dnsmasq_range', 'dnsmasq_option', + 'dnsmasq_boot', 'dnsmasq_tag', 'haproxy_general_settings', 'haproxy_general_cache', 'haproxy_general_defaults', + 'haproxy_general_logging', 'haproxy_general_peers', 'haproxy_general_stats', 'haproxy_general_tuning', + 'haproxy_maintenance', 'haproxy_cpu', 'haproxy_user', 'haproxy_group', 'haproxy_acl', 'haproxy_action', + 'haproxy_lua', 'haproxy_fcgi', 'haproxy_errorfile', 'wazuh_agent', +] + + +def run_module(): + module_args = dict( + target=dict( + type='str', required=True, aliases=['tgt', 't'], + choices=TARGETS, + description='What part of the running config should be listed' + ), + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + target = module.params['target'] + Target_Obj, target_inst = None, None + + try: + # NOTE: dynamic imports not working as Ansible will not copy those modules to the temporary directory + # the module is executed in! + # see: ansible.executor.module_common.ModuleDepFinder (analyzing imports to know what dependencies to copy) + + if target == 'alias': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.alias import \ + Alias as Target_Obj + + elif target == 'rule': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.rule import \ + Rule as Target_Obj + + elif target == 'rule_interface_group': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.rule_interface_group import \ + Group as Target_Obj + + elif target == 'route': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.route import \ + Route as Target_Obj + + elif target == 'gateway': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.gateway import \ + Gw as Target_Obj + + elif target == 'cron': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.cron import \ + CronJob as Target_Obj + + elif target == 'unbound_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_general import \ + General as Target_Obj + + elif target == 'unbound_acl': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_acl import \ + Acl as Target_Obj + + elif target == 'unbound_host': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_host import \ + Host as Target_Obj + + elif target == 'unbound_host_alias': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_host_alias \ + import Alias as Target_Obj + + elif target == 'unbound_dot': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_dot \ + import DnsOverTls as Target_Obj + + elif target == 'unbound_forward': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_forward \ + import Forward as Target_Obj + + elif target == 'unbound_dnsbl': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_dnsbl import \ + DnsBL as Target_Obj + + elif target == 'syslog': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.syslog import \ + Syslog as Target_Obj + + elif target == 'package': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.package import Package + target_inst = Package(module=module, name='dummy') + + elif target == 'ipsec_cert': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_cert import \ + KeyPair as Target_Obj + + elif target == 'ipsec_psk': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_psk import \ + PreSharedKey as Target_Obj + + elif target == 'shaper_pipe': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.shaper_pipe import \ + Pipe as Target_Obj + + elif target == 'shaper_queue': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.shaper_queue import \ + Queue as Target_Obj + + elif target == 'shaper_rule': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.shaper_rule import \ + Rule as Target_Obj + + elif target == 'monit_service': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.monit_service import \ + Service as Target_Obj + + elif target == 'monit_test': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.monit_test import \ + Test as Target_Obj + + elif target == 'monit_alert': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.monit_alert import \ + Alert as Target_Obj + + elif target == 'wireguard_server': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wireguard_server \ + import Server as Target_Obj + + elif target == 'wireguard_peer': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wireguard_peer import \ + Peer as Target_Obj + + elif target == 'interface_vlan': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vlan import \ + Vlan as Target_Obj + + elif target == 'interface_vxlan': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vxlan import \ + Vxlan as Target_Obj + + elif target == 'interface_lagg': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_lagg import \ + Lagg as Target_Obj + + elif target == 'interface_loopback': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_loopback import \ + Loopback as Target_Obj + + elif target == 'interface_gre': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_gre import \ + Gre as Target_Obj + + elif target == 'interface_bridge': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_bridge import \ + Bridge as Target_Obj + + elif target == 'interface_gif': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_gif import \ + Gif as Target_Obj + + elif target in ['source_nat', 'nat_source']: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.nat_source import \ + SNat as Target_Obj + + elif target == 'nat_one_to_one': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.nat_one_to_one import \ + OneToOne as Target_Obj + + elif target == 'frr_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_general \ + import General as Target_Obj + + elif target == 'frr_bfd_neighbor': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bfd_neighbor import \ + Neighbor as Target_Obj + + elif target == 'frr_bgp_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_general \ + import General as Target_Obj + + elif target == 'frr_bgp_neighbor': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_neighbor \ + import Neighbor as Target_Obj + + elif target == 'frr_bgp_prefix_list': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_prefix_list \ + import Prefix as Target_Obj + + elif target == 'frr_bgp_route_map': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_route_map \ + import RouteMap as Target_Obj + + elif target == 'frr_bgp_community_list': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_community_list \ + import Community as Target_Obj + + elif target == 'frr_bgp_as_path': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_as_path \ + import AsPath as Target_Obj + + elif target == 'frr_bgp_redistribution': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_redistribution \ + import Redistribution as Target_Obj + + elif target == 'frr_bgp_peer_group': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_peer_group \ + import PeerGroup as Target_Obj + + elif target == 'frr_ospf_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_general \ + import General as Target_Obj + + elif target == 'frr_ospf3_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_general \ + import General as Target_Obj + + elif target == 'frr_ospf3_interface': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_interface \ + import Interface as Target_Obj + + elif target == 'frr_ospf_prefix_list': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_prefix_list \ + import Prefix as Target_Obj + + elif target == 'frr_ospf_interface': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_interface \ + import Interface as Target_Obj + + elif target == 'frr_ospf_route_map': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_route_map \ + import RouteMap as Target_Obj + + elif target == 'frr_ospf_network': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_network \ + import Network as Target_Obj + + elif target == 'frr_ospf_redistribution': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf_redistribution \ + import Redistribution as Target_Obj + + elif target == 'frr_ospf3_redistribution': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_redistribution \ + import Redistribution as Target_Obj + + elif target == 'frr_ospf3_route_map': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_route_map \ + import RouteMap as Target_Obj + + elif target == 'frr_ospf3_network': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_network \ + import Network as Target_Obj + + elif target == 'frr_ospf3_prefix_list': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_ospf3_prefix_list \ + import Prefix as Target_Obj + + elif target == 'frr_rip': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_rip \ + import Rip as Target_Obj + + elif target == 'bind_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_general \ + import General as Target_Obj + + elif target == 'bind_blocklist': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_blocklist \ + import Blocklist as Target_Obj + + elif target == 'bind_acl': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_acl \ + import Acl as Target_Obj + + elif target == 'bind_domain': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_domain \ + import Domain as Target_Obj + + elif target == 'bind_record': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_record \ + import Record as Target_Obj + + elif target == 'interface_vip': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vip import \ + Vip as Target_Obj + + elif target == 'webproxy_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_general import \ + General as Target_Obj + + elif target == 'webproxy_cache': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_cache import \ + Cache as Target_Obj + + elif target == 'webproxy_traffic': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_traffic import \ + Traffic as Target_Obj + + elif target == 'webproxy_parent': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_parent import \ + Parent as Target_Obj + + elif target == 'webproxy_forward': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_forward import \ + General as Target_Obj + + elif target == 'webproxy_acl': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_acl import \ + General as Target_Obj + + elif target == 'webproxy_icap': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_icap import \ + General as Target_Obj + + elif target == 'webproxy_auth': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_auth import \ + General as Target_Obj + + elif target == 'webproxy_remote_acl': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_remote_acl import \ + Acl as Target_Obj + + elif target == 'webproxy_pac_proxy': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_pac_proxy import \ + Proxy as Target_Obj + + elif target == 'webproxy_pac_match': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_pac_match import \ + Match as Target_Obj + + elif target == 'webproxy_pac_rule': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_pac_rule import \ + Rule as Target_Obj + + elif target == 'nginx_upstream_server': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.nginx_upstream_server import \ + UpstreamServer as Target_Obj + + elif target == 'ipsec_connection': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_connection import \ + Connection as Target_Obj + + elif target == 'ipsec_pool': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_pool import \ + Pool as Target_Obj + + elif target == 'ipsec_child': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_child import \ + Child as Target_Obj + + elif target == 'ipsec_vti': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_vti import \ + Vti as Target_Obj + + elif target == 'ipsec_auth_local': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_auth_local import \ + Auth as Target_Obj + + elif target == 'ipsec_auth_remote': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_auth_remote import \ + Auth as Target_Obj + + elif target == 'ipsec_manual_spd': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_manual_spd import \ + ManualSPD as Target_Obj + + elif target == 'ipsec_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_general import \ + General as Target_Obj + + elif target == 'ids_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_general import \ + General as Target_Obj + + elif target == 'ids_policy': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_policy import \ + Policy as Target_Obj + + elif target == 'ids_rule': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_rule import \ + Rule as Target_Obj + + elif target == 'ids_ruleset': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_ruleset import \ + Ruleset as Target_Obj + + elif target == 'ids_user_rule': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_user_rule import \ + Rule as Target_Obj + + elif target == 'ids_policy_rule': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_policy_rule import \ + Rule as Target_Obj + + elif target == 'openvpn_instance': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.openvpn_client import \ + Client as Target_Obj + + elif target == 'openvpn_static_key': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.openvpn_static_key import \ + Key as Target_Obj + + elif target == 'openvpn_client_override': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.openvpn_client_override import \ + Override as Target_Obj + + elif target == 'dhcrelay_destination': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcrelay_destination import \ + DhcRelayDestination as Target_Obj + + elif target == 'dhcrelay_relay': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcrelay_relay import \ + DhcRelayRelay as Target_Obj + + elif target == 'dhcp_reservation': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcp_reservation_v4 import \ + ReservationV4 as Target_Obj + + elif target == 'dhcp_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcp_general import \ + General as Target_Obj + + elif target == 'dhcp_subnet': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcp_subnet_v4 import \ + SubnetV4 as Target_Obj + + elif target == 'acme_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_general import \ + General as Target_Obj + + elif target == 'acme_account': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_account import \ + Account as Target_Obj + + elif target == 'acme_validation': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_validation import \ + Validation as Target_Obj + + elif target == 'acme_action': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_action import \ + Action as Target_Obj + + elif target == 'acme_certificate': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.acme_certificate import \ + Certificate as Target_Obj + + elif target == 'postfix_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_general import \ + General as Target_Obj + + elif target == 'postfix_domain': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_domain import \ + Domain as Target_Obj + + elif target == 'postfix_recipient': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_recipient import \ + Recipient as Target_Obj + + elif target == 'postfix_recipientbcc': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_recipientbcc import \ + RecipientBCC as Target_Obj + + elif target == 'postfix_sender': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_sender import \ + Sender as Target_Obj + + elif target == 'postfix_senderbcc': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_senderbcc import \ + SenderBCC as Target_Obj + + elif target == 'postfix_sendercanonical': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_sendercanonical import \ + SenderCanonical as Target_Obj + + elif target == 'postfix_headercheck': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_headercheck import \ + Headercheck as Target_Obj + + elif target == 'postfix_address': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_address import \ + Address as Target_Obj + + elif target == 'hasync_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.hasync_general import \ + General as Target_Obj + + elif target == 'snapshot': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.snapshot import \ + Snapshot as Target_Obj + + elif target == 'wazuh_agent': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wazuh_agent import \ + WazuhAgent as Target_Obj + + elif target == 'user': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.user import \ + User as Target_Obj + + elif target == 'group': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.group import \ + Group as Target_Obj + + elif target == 'privilege': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.privilege import \ + Privilege as Target_Obj + + elif target == 'neighbor': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.neighbor import \ + Neighbor as Target_Obj + + elif target == 'dnsmasq_general': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_general import \ + General as Target_Obj + + elif target == 'dnsmasq_domain': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_domain import \ + Domain as Target_Obj + + elif target == 'dnsmasq_host': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_host import \ + Host as Target_Obj + + elif target == 'dnsmasq_range': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_range import \ + Range as Target_Obj + + elif target == 'dnsmasq_option': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_option import \ + Option as Target_Obj + + elif target == 'dnsmasq_boot': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_boot import \ + Boot as Target_Obj + + elif target == 'dnsmasq_tag': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_tag import \ + Tag as Target_Obj + + elif target == 'haproxy_general_settings': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_settings import \ + HaproxyGeneralSettings as Target_Obj + + elif target == 'haproxy_general_cache': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_cache import \ + HaproxyGeneralCache as Target_Obj + + elif target == 'haproxy_general_defaults': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_defaults import \ + HaproxyGeneralDefaults as Target_Obj + + elif target == 'haproxy_general_logging': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_logging import \ + HaproxyGeneralLogging as Target_Obj + + elif target == 'haproxy_general_peers': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_peers import \ + HaproxyGeneralPeers as Target_Obj + + elif target == 'haproxy_general_stats': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_stats import \ + HaproxyGeneralStats as Target_Obj + + elif target == 'haproxy_general_tuning': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_tuning import \ + HaproxyGeneralTuning as Target_Obj + + elif target == 'haproxy_maintenance': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_maintenance import \ + HaproxyMaintenance as Target_Obj + + elif target == 'haproxy_cpu': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_cpu import \ + HaproxyCpu as Target_Obj + + elif target == 'haproxy_user': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_user import \ + HaproxyUser as Target_Obj + + elif target == 'haproxy_group': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_group import \ + HaproxyGroup as Target_Obj + + elif target == 'haproxy_acl': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_acl import \ + HaproxyAcl as Target_Obj + + elif target == 'haproxy_action': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_action import \ + HaproxyAction as Target_Obj + + elif target == 'haproxy_lua': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_lua import \ + HaproxyLua as Target_Obj + + elif target == 'haproxy_fcgi': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_fcgi import \ + HaproxyFcgi as Target_Obj + + elif target == 'haproxy_errorfile': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_errorfile import \ + HaproxyErrorfile as Target_Obj + + except AttributeError: + module_dependency_error() + + result['data'] = None + + if Target_Obj is not None or target_inst is not None: + if target_inst is None: + target_inst = Target_Obj(module=module, result=result) + + if hasattr(target_inst, 'get_existing'): + # has additional filtering + target_func = getattr(target_inst, 'get_existing') + + elif hasattr(target_inst, 'search_call'): + target_func = getattr(target_inst, 'search_call') + + elif hasattr(target_inst, '_search_call'): + target_func = getattr(target_inst, '_search_call') + + else: + target_func = getattr(target_inst.b, 'get_existing') + + result['data'] = target_func() + + if hasattr(target_inst, 's'): + target_inst.s.close() + + else: + module.fail_json(f"Got unsupported target: '{target}'") + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_alert.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_alert.py new file mode 100644 index 0000000..ba537da --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_alert.py @@ -0,0 +1,120 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/monit.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.monit_alert import Alert + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/monit.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/monit.html' + + +def run_module(): + module_args = dict( + recipient=dict( + type='str', required=True, aliases=['email', 'mail'], + description='The email address to send alerts to', + ), + not_on=dict( + type='bool', required=False, default=False, aliases=['not'], + description='Do not send alerts for the following events but on all others', + ), + events=dict( + type='list', elements='str', required=False, default=[], + choices=[ + 'action', 'checksum', 'bytein', 'byteout', 'connection', 'content', + 'data', 'exec', 'fsflags', 'gid', 'icmp', 'instance', 'invalid', + 'link', 'nonexist', 'packetin', 'packetout', 'permission', 'pid', + 'ppid', 'resource', 'saturation', 'size', 'speed', 'status', + 'timeout', 'timestamp', 'uid', 'uptime' + ], + description="Values: " + "'action' = 'Action done', " + "'checksum' = 'Checksum failed', " + "'bytein' = 'Download bytes exceeded', " + "'byteout' = 'Upload bytes exceeded', " + "'connection' = 'Connection failed', " + "'content' = 'Content failed', " + "'data' = 'Data access error', " + "'exec' = 'Execution failed', " + "'fsflags' = 'Filesystem flags failed', " + "'gid' = 'GID failed', " + "'icmp' = 'Ping failed', " + "'instance' = 'Monit instance changed', " + "'invalid' = 'Invalid type', " + "'link' = 'Link down', " + "'nonexist' = 'Does not exist', " + "'packetin' = 'Download packets exceeded', " + "'packetout' = 'Upload packets exceeded', " + "'permission' = 'Permission failed', " + "'pid' = 'PID failed', " + "'ppid' = 'PPID failed', " + "'resource' = 'Resource limit matched', " + "'saturation' = 'Saturation exceeded', " + "'size' = 'Size failed', " + "'speed' = 'Speed failed', " + "'status' = 'Status failed', " + "'timestamp' = 'Timestamp failed', " + "'uid' = 'UID failed', " + "'uptime' = 'Uptime failed'" + ), + format=dict( + type='str', required=False, + description='The email format for alerts. Subject: $SERVICE on $HOST failed' + ), + reminder=dict( + type='int', required=False, default=10, + description='Send a reminder after some cycles', + ), + description=dict(type='str', required=False, aliases=['desc']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured alerts with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['recipient', 'not_on', 'events', 'reminder', 'description'], + default=['recipient'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Alert(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_service.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_service.py new file mode 100644 index 0000000..e7bc4f4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_service.py @@ -0,0 +1,103 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/monit.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.monit_service import Service + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/monit.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/monit.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True, description='Unique service name'), + type=dict( + type='str', required=False, + choices=[ + 'process', 'file', 'fifo', 'filesystem', 'directory', 'host', 'system', + 'custom', 'network', + ] + ), + pidfile=dict(type='path', required=False), + match=dict(type='str', required=False), + path=dict( + type='path', required=False, + description='According to the service type path can be a file or a directory', + ), + service_timeout=dict(type='int', required=False, default=300, aliases=['svc_timeout']), + address=dict( + type='str', required=False, + description="The target IP address for 'Remote Host' and 'Network' checks", + ), + interface=dict( + type='str', required=False, + description="The existing Interface for 'Network' checks" + ), + start=dict( + type='str', required=False, + description='Absolute path to the executable with its arguments to run ' + 'at service-start', + ), + stop=dict( + type='str', required=False, + description='Absolute path to the executable with its arguments to run ' + 'at service-stop', + ), + tests=dict(type='list', elements='str', required=False, default=[]), + depends=dict( + type='list', elements='str', required=False, default=[], + description='Optionally define a (list of) service(s) which are required ' + 'before monitoring this one, if any of the dependencies are either ' + 'stopped or unmonitored this service will stop/unmonitor too', + ), + polltime=dict( + type='str', required=False, + description='Set the service poll time. Either as a number of cycles ' + "'NUMBER CYCLES' or Cron-style '* 8-19 * * 1-5'" + ), + description=dict(type='str', required=False, aliases=['desc']), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Service(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_test.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_test.py new file mode 100644 index 0000000..2b2f512 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/monit_test.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/monit.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.monit_test import Test + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/monit.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/monit.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True, description='Unique test name'), + type=dict( + type='str', required=False, default='Custom', + choices=[ + 'Existence', 'SystemResource', 'ProcessResource', 'ProcessDiskIO', + 'FileChecksum', 'Timestamp', 'FileSize', 'FileContent', 'FilesystemMountFlags', + 'SpaceUsage', 'InodeUsage', 'DiskIO', 'Permisssion', 'UID', 'GID', 'PID', 'PPID', + 'Uptime', 'ProgramStatus', 'NetworkInterface', 'NetworkPing', 'Connection', 'Custom', + ], + description='Custom will not be idempotent - will be translated on the server-side. ' + "See 'list' module output for details" + ), + condition=dict( + type='str', required=False, + description="The test condition. Per example: " + "'cpu is greater than 50%' or " + "'failed host 127.0.0.1 port 22 protocol ssh'" + ), + action=dict( + type='str', required=False, default='alert', + choices=['alert', 'restart', 'start', 'stop', 'exec', 'unmonitor'] + ), + path=dict( + type='path', required=False, + description='The absolute path to the script to execute - if action is ' + "set to 'execute'. " + "Make sure the script is executable by the Monit service" + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Test(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nat_one_to_one.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nat_one_to_one.py new file mode 100644 index 0000000..0b65cc2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nat_one_to_one.py @@ -0,0 +1,99 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.rule import \ + RULE_MOD_ARGS, RULE_MOD_ARG_ALIASES + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.nat_one_to_one import OneToOne + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/nat_one_to_one.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/nat_one_to_one.html' + + +def run_module(): + shared_rule_args = { + 'log': RULE_MOD_ARGS['log'], + 'sequence': RULE_MOD_ARGS['sequence'], + 'source_net': RULE_MOD_ARGS['source_net'], + 'source_invert': RULE_MOD_ARGS['source_invert'], + 'destination_net': RULE_MOD_ARGS['destination_net'], + 'destination_invert': RULE_MOD_ARGS['destination_invert'], + 'description': RULE_MOD_ARGS['description'], + 'uuid': RULE_MOD_ARGS['uuid'], + } + + module_args = dict( + interface=dict( + type='str', required=False, aliases=RULE_MOD_ARG_ALIASES['interface'], + description='Interfaces use this rule on', + ), + type=dict( + type='str', required=False, choices=['binat', 'nat'], default='binat', + description='Select `binat` (default) or `nat`. When nets are equally sized binat is usually the ' + 'best option. Using NAT we can also map unequal sized networks. A BINAT rule specifies a ' + 'bidirectional mapping between an external and internal network and can be used from both ' + 'ends, nat only applies in one direction.', + ), + external=dict( + type='str', required=False, aliases=['external_net', 'ext', 'e'], + description='The external subnet\'s starting address for the 1:1 mapping or network. This is the address ' + 'or network the traffic will translate to/from.' + ), + nat_reflection=dict(type='str', required=False, choices=['enable', 'disable'], aliases=['natreflection']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured rules with the running config - ' + "if any of those fields are changed, the module will think it's a new rule", + choices=[ + 'sequence', 'interface', 'source_net', 'source_invert', 'destination_net', 'destination_invert', + 'description', 'uuid', 'type', 'external', 'nat_reflection', + ], + default=['interface', 'external'] + ), + **shared_rule_args, + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('ensure', 'present', ('interface', 'external', )), + ], + ) + + module_wrapper(OneToOne(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nat_source.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nat_source.py new file mode 100644 index 0000000..3168c6a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nat_source.py @@ -0,0 +1,97 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/firewall.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.rule import \ + RULE_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.nat_source import SNat + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/nat_source.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/nat_source.html' + + +def run_module(): + shared_rule_args = { + 'sequence': RULE_MOD_ARGS['sequence'], + 'ip_protocol': RULE_MOD_ARGS['ip_protocol'], + 'protocol': RULE_MOD_ARGS['protocol'], + 'source_invert': RULE_MOD_ARGS['source_invert'], + 'source_net': RULE_MOD_ARGS['source_net'], + 'source_port': RULE_MOD_ARGS['source_port'], + 'destination_invert': RULE_MOD_ARGS['destination_invert'], + 'destination_net': RULE_MOD_ARGS['destination_net'], + 'destination_port': RULE_MOD_ARGS['destination_port'], + 'log': RULE_MOD_ARGS['log'], + 'uuid': RULE_MOD_ARGS['uuid'], + 'description': RULE_MOD_ARGS['description'], + } + + module_args = dict( + no_nat=dict( + type='bool', required=False, default=False, + description='Enabling this option will disable NAT for traffic matching ' + 'this rule and stop processing Outbound NAT rules.' + ), + interface=dict(type='str', required=False, aliases=['int', 'i']), + target=dict( + type='str', required=False, aliases=['tgt', 't'], + description='NAT translation target - Packets matching this rule will be ' + 'mapped to the IP address given here.', + ), + target_port=dict(type='int', required=False, aliases=['nat_port', 'np']), + match_fields=dict( + type='list', required=True, elements='str', + description='Fields that are used to match configured rules with the running config - ' + "if any of those fields are changed, the module will think it's a new rule", + choices=[ + 'sequence', 'interface', 'target', 'target_port', 'ip_protocol', 'protocol', + 'source_invert', 'source_net', 'source_port', 'destination_invert', 'destination_net', + 'destination_port', 'description', 'uuid', + ] + ), + **shared_rule_args, + **STATE_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(SNat(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/neighbor.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/neighbor.py new file mode 100644 index 0000000..dd4f1ca --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/neighbor.py @@ -0,0 +1,70 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.neighbor import Neighbor + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/interface.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/interface.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc'], + description='The unique description used to match the configured entries to the existing ones.', + ), + ethernet_address=dict( + type='str', required=False, aliases=['mac'], + description='Hardware MAC address of the neighbor (format xx:xx:xx:xx:xx:xx).', + ), + ip_address=dict( + type='str', required=False, aliases=['ip'], + description='IP address to assign to the provided MAC address.', + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('ethernet_address', 'ip_address')), + ], + ) + + module_wrapper(Neighbor(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nginx_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nginx_general.py new file mode 100644 index 0000000..84f7150 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nginx_general.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/nginx.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + EN_ONLY_MOD_ARG, OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.nginx_general import General + + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/nginx.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/nginx.html' + + +def run_module(): + module_args = dict( + ban_ttl=dict(type='int', required=False, default=0), + **EN_ONLY_MOD_ARG, + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nginx_upstream_server.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nginx_upstream_server.py new file mode 100644 index 0000000..7312c74 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/nginx_upstream_server.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG_DEF_FALSE + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.nginx_upstream_server import UpstreamServer + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/nginx.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/nginx.html' + + +def run_module(): + module_args = dict( + description=dict(type='str', alias=['name'], required=True, aliases=['name']), + server=dict(type='str', required=False), + port=dict(type='int', required=False), + priority=dict(type='int', required=False), + max_conns=dict(type='int', required=False), + max_fails=dict(type='int', required=False), + fail_timeout=dict(type='int', required=False), + no_use=dict( + type='str', required=False, choices=['', 'down', 'backup'], default='', + ), + **RELOAD_MOD_ARG_DEF_FALSE, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + + module_wrapper(UpstreamServer(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_client.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_client.py new file mode 100644 index 0000000..a0aa2b1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_client.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.openvpn import \ + OPENVPN_INSTANCE_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.openvpn_client import Client + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' + +def run_module(): + module_args = dict( + # general + remote=dict( + type='list', elements='str', required=False, aliases=['peer', 'server'], + description='Remote host name or IP address with optional port' + ), + port=dict( + type='int', required=False, aliases=['local_port', 'bind_port'], + description='Port number to use.' + 'Specifies a bind address, or nobind when client does not have a specific bind address.' + ), + carp_depend_on=dict( + aliases=['vip', 'vip_depend', 'carp', 'carp_depend'], + type='str', required=False, + description='The carp VHID to depend on, when this virtual address is not in ' + 'master state, the interface cost will be set to the demoted cost' + ), + # auth + username=dict( + type='str', required=False, aliases=['user'], + description='(optional) Username to send to the server for authentication when required.' + ), + password=dict( + type='str', required=False, aliases=['pwd'], no_log=True, + description='Password belonging to the user specified above' + ), + # misc + http_proxy=dict( + type='str', required=False, aliases=['proxy'], + description='Use a http proxy to connect to the selected server, define as host:port' + ), + **OPENVPN_INSTANCE_MOD_ARGS, + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Client(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_client_override.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_client_override.py new file mode 100644 index 0000000..c36c1bf --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_client_override.py @@ -0,0 +1,139 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.openvpn_client_override import Override + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['x509', 'common_name'], + description="The client's X.509 common-name used to match these override to" + ), + servers=dict( + type='list', elements='str', required=False, aliases=['instances'], + description='Select the OpenVPN servers where this override applies to, leave empty for all' + ), + description=dict( + type='str', required=False, aliases=['desc'], + description='You may enter a description here for your reference (not parsed).' + ), + block=dict( + type='bool', required=False, default=False, aliases=['block_connection', 'block_client'], + description="Block this client connection based on its common name. Don't use this option to permanently " + "disable a client due to a compromised key or password. " + "Use a CRL (certificate revocation list) instead." + ), + push_reset=dict( + type='bool', required=False, default=False, aliases=['reset'], + description="Don't inherit the global push list for a specific client instance. NOTE: --push-reset is " + "very thorough: it will remove almost all options from the list of to-be-pushed options. " + "In many cases, some of these options will need to be re-configured afterwards - " + "specifically, --topology subnet and --route-gateway will get lost and this will break " + "client configs in many cases." + ), + network_tunnel_ip4=dict( + type='str', required=False, aliases=['tun_ip4', 'tunnel_ip4'], + description='Push virtual IP endpoints for client tunnel, overriding dynamic allocation.' + ), + network_tunnel_ip6=dict( + type='str', required=False, aliases=['tun_ip6', 'tunnel_ip6'], + description='Push virtual IP endpoints for client tunnel, overriding dynamic allocation.' + ), + network_local=dict( + type='list', elements='str', required=False, default=[], aliases=['net_local', 'push_route'], + description='These are the networks accessible by the client, these are pushed via route{-ipv6} ' + 'clauses in OpenVPN to the client.' + ), + network_remote=dict( + type='list', elements='str', required=False, default=[], aliases=['net_remote', 'route'], + description='Remote networks for the server, these are configured via iroute{-ipv6} clauses in OpenVPN ' + 'and inform the server to send these networks to this specific client.' + ), + route_gateway=dict( + type='str', required=False, aliases=['route_gw', 'rt_gw'], + description='Specify a default gateway to use for the connected client. Without one set the first ' + 'address in the netblock is being offered. When segmenting the tunnel (server) network, ' + 'this one might not be accessible from the client.' + ), + redirect_gateway=dict( + type='list', elements='str', required=False, default=[], aliases=['redirect_gw', 'redir_gw'], + choices=['local', 'autolocal', 'def1', 'bypass-dhcp', 'bypass-dns', 'block-local', 'ipv6', '!ipv4'], + description='Automatically execute routing commands to cause all outgoing IP traffic to be ' + 'redirected over the VPN.' + ), + register_dns=dict( + type='bool', required=False, default=False, + description='Run ipconfig /flushdns and ipconfig /registerdns on connection initiation. ' + 'This is known to kick Windows into recognizing pushed DNS servers.' + ), + domain=dict( + type='str', required=False, aliases=['dns_domain'], + description='Set Connection-specific DNS Suffix.' + ), + domain_list=dict( + type='list', elements='str', required=False, default=[], aliases=['dns_domain_search'], + description='Add name to the domain search list. Repeat this option to add more entries. ' + 'Up to 10 domains are supported.' + ), + dns_servers=dict( + type='list', elements='str', required=False, default=[], aliases=['dns'], + description='Set primary domain name server IPv4 or IPv6 address. ' + 'Repeat this option to set secondary DNS server addresses.' + ), + ntp_servers=dict( + type='list', elements='str', required=False, default=[], aliases=['ntp'], + description='Set primary NTP server address (Network Time Protocol). ' + 'Repeat this option to set secondary NTP server addresses.' + ), + wins_servers=dict( + type='list', elements='str', required=False, default=[], aliases=['wins'], + description='Set primary WINS server address (NetBIOS over TCP/IP Name Server). ' + 'Repeat this option to set secondary WINS server addresses.' + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Override(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_server.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_server.py new file mode 100644 index 0000000..a5d6425 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_server.py @@ -0,0 +1,242 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# template to be copied to implement new modules + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.openvpn import \ + OPENVPN_INSTANCE_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.openvpn_server import Server + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' + +USER_CN_STRICT_MAP = { + 'no': 0, + 'false': 0, + 'False': 0, + 'yes': 1, + 'true': 1, + 'True': 1, + 'case-insensitive': 2, + 'ci': 2, +} + +def run_module(): + module_args = dict( + # general + port=dict( + type='int', required=False, default=1194, aliases=['local_port', 'bind_port'], + description='Port number to use' + ), + port_share=dict( + type='str', required=False, + description='Proxy the connection to the given host:port when a non-OpenVPN protocol ' + 'connections is detected.' + ), + server_ip4=dict( + type='str', required=False, aliases=['server', 'client_net_ip4', 'net_ip4'], + description='This directive will set up an OpenVPN server which will allocate addresses to clients ' + 'out of the given network/netmask. The server itself will take the .1 address of the given ' + 'network for use as the server-side endpoint of the local TUN/TAP interface' + ), + server_ip6=dict( + type='str', required=False, aliases=['server6', 'client_net_ip6', 'net_ip6'], + description='This directive will set up an OpenVPN server which will allocate addresses to clients ' + 'out of the given network/netmask. The server itself will take the next base address (+1) ' + 'of the given network for use as the server-side endpoint of the local TUN/TAP interface' + ), + pool=dict( + type='bool', required=False, default=True, + description='Set up a dynamic pool for the server directive. IP addresses will otherwise only be pushed ' + 'to a client if specified in a CSO.' + ), + max_connections=dict( + type='int', required=False, aliases=['max_conn', 'max_clients'], + description='Specify the maximum number of clients allowed to concurrently connect to this server.' + ), + topology=dict( + type='str', required=False, default='subnet', aliases=['topo'], + choices=['net30', 'p2p', 'subnet'], + description='Configure virtual addressing topology when running in --dev tun mode. This directive ' + 'has no meaning in --dev tap mode, which always uses a subnet topology.' + ), + # trust + crl=dict( + type='str', required=False, aliases=['certificate_revocation_list', 'revocation_list'], + description='Select a certificate revocation list to use for this service.' + ), + verify_client_cert=dict( + type='str', required=False, default='require', aliases=['verify_client', 'verify_cert'], + choices=['require', 'none'], + description='Specify if the client is required to offer a certificate.' + ), + cert_depth=dict( + type='int', required=False, aliases=['certificate_depth'], + choices=[1, 2, 3, 4, 5], + description='When a certificate-based client logs in, do not accept certificates below this depth. ' + 'Useful for denying certificates made with intermediate CAs generated from the same CA as ' + 'the server. ' + '0 = Do not check, 1 = Client+Server, 2 = Client+Intermediate+Server, ' + '3 = Client+2xInter+Server, 4 = Client+3xInter+Server, ' + '5 = Client+4xInter+Server' + ), + data_ciphers=dict( + type='list', elements='str', required=False, default=[], aliases=['ciphers'], + choices=['AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305'], + description='Restrict the allowed ciphers to be negotiated to the ciphers in this list.' + ), + data_cipher_fallback=dict( + type='str', required=False, aliases=['cipher_fallback'], + choices=['AES-256-GCM', 'AES-128-GCM', 'CHACHA20-POLY1305'], + description='Configure a cipher that is used to fall back to if we could not determine which cipher the ' + 'peer is willing to use. This option should only be needed to connect to peers that are ' + 'running OpenVPN 2.3 or older versions, and have been configured with --enable-small ' + '(typically used on routers or other embedded devices).' + ), + ocsp=dict( + type='bool', required=False, default=False, aliases=['use_ocsp', 'verify_ocsp'], + description='When the CA used supplies an authorityInfoAccess OCSP URI extension, ' + 'it will be used to validate the client certificate.' + ), + # authentication + auth_mode=dict( + type='list', elements='str', required=False, default=[], + aliases=['authentication_mode', 'auth_source'], + description='Select authentication methods to use, leave empty if no challenge response ' + 'authentication is needed.' + ), + auth_group=dict( + type='str', required=False, aliases=['group'], + description='Restrict access to users in the selected local group. Please be aware that other ' + 'authentication backends will refuse to authenticate when using this option.' + ), + user_as_cn=dict( + type='bool', required=False, default=False, aliases=['username_as_cn'], + description='Use the authenticated username as the common-name, rather than the common-name ' + 'from the client certificate.' + ), + user_cn_strict=dict( + type='str', required=False, default=False, aliases=['username_cn_strict'], + choices=list(USER_CN_STRICT_MAP.keys()), + description='When authenticating users, enforce a match between the Common Name of the client ' + 'certificate and the username given at login.' + ), + auth_token_time=dict( + type='int', required=False, aliases=['auth_time', 'token_time'], + description='After successful user/password authentication, the OpenVPN server will with this option ' + 'generate a temporary authentication token and push that to the client. On the following ' + 'renegotiations, the OpenVPN client will pass this token instead of the users password. ' + 'On the server side the server will do the token authentication internally and it will NOT ' + 'do any additional authentications against configured external user/password authentication ' + 'mechanisms. When set to 0, the token will never expire, any other value specifies the ' + 'lifetime in seconds.' + ), + auth_token_renewal=dict( + type='int', required=False, aliases=['auth_renewal', 'token_renewal'], + description='How often the auth token will be renewed, token expire after 2 * renewal time.' + ), + auth_token_secret=dict( + type='string', required=False, aliases=['auth_secret', 'token_secret'], no_log=True, + description=' Optional secret for use with auth-gen-token. This is useful to allow failover between ' + 'multiple servers without user interaction.' + ), + require_client_provisioning=dict( + type='bool', required=False, default=False, aliases=['provision_exclusive'], + description='Require, as a condition for authentication, that a tunnel address will be provisioned ' + 'either from a local defined client-specific override or offered by an authenticator ' + '(such as RADIUS).' + ), + # misc + push_options=dict( + type='list', elements='str', required=False, default=[], aliases=['push_opts'], + choices=['block-outside-dns', 'register-dns'], + description='Various less frequently used yes/no options which can be pushed to the client ' + 'for this instance.', + ), + redirect_gateway=dict( + type='list', elements='str', required=False, default=[], aliases=['redirect_gw', 'redir_gw'], + choices=['local', 'autolocal', 'def1', 'bypass-dhcp', 'bypass-dns', 'block-local', 'ipv6', '!ipv4'], + description='Automatically execute routing commands to cause all outgoing IP traffic to be ' + 'redirected over the VPN.', + ), + route_metric=dict( + type='int', required=False, aliases=['metric', 'push_metric'], + description='Specify a default metric m for use with --route on the connecting client (push option).' + ), + register_dns=dict( + type='bool', required=False, default=False, + description='Run ipconfig /flushdns and ipconfig /registerdns on connection initiation. ' + 'This is known to kick Windows into recognizing pushed DNS servers.' + ), + domain=dict( + type='str', required=False, aliases=['dns_domain'], + description='Set Connection-specific DNS Suffix.' + ), + domain_list=dict( + type='list', elements='str', required=False, default=[], aliases=['dns_domain_search'], + description='Add name to the domain search list. Repeat this option to add more entries. ' + 'Up to 10 domains are supported' + ), + dns_servers=dict( + type='list', elements='str', required=False, default=[], aliases=['dns'], + description='Set primary domain name server IPv4 or IPv6 address. ' + 'Repeat this option to set secondary DNS server addresses.' + ), + ntp_servers=dict( + type='list', elements='str', required=False, default=[], aliases=['ntp'], + description='Set primary NTP server address (Network Time Protocol). ' + 'Repeat this option to set secondary NTP server addresses.' + ), + persist_address_pool=dict( + type='bool', required=False, default=False, + description='Save ip address pool to disk.' + ), + **OPENVPN_INSTANCE_MOD_ARGS, + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module.params['user_cn_strict'] = USER_CN_STRICT_MAP[module.params['user_cn_strict']] + + module_wrapper(Server(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_static_key.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_static_key.py new file mode 100644 index 0000000..c27cde9 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_static_key.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.openvpn_static_key import Key + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['desc', 'description'], + description='The name used to match this config to existing entries' + ), + mode=dict( + type='str', required=False, default='crypt', aliases=['type'], choices=['auth', 'crypt'], + description='Define the use of this key, authentication (--tls-auth) or authentication and ' + 'encryption (--tls-crypt)' + ), + key=dict( + type='str', required=False, no_log=True, + description='OpenVPN Static key. If empty - it will be auto-generated.' + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Key(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_status.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_status.py new file mode 100644 index 0000000..fa0f9dc --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/openvpn_status.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import single_get + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/openvpn.html' + +TARGET_MAPPING = { + 'sessions': 'searchSessions', + 'routes': 'searchRoutes', +} + + +def run_module(): + module_args = dict( + target=dict( + type='str', required=False, default='sessions', aliases=['kind'], + choices=['sessions', 'routes'], + description='What information to query' + ), + **OPN_MOD_ARGS, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + info = single_get( + module=module, + cnf={ + 'module': 'openvpn', + 'controller': 'service', + 'command': TARGET_MAPPING[module.params['target']], + } + ) + + if 'rows' in info: + info = info['rows'] + + if isinstance(info, str): + info = info.strip() + + module.exit_json(data=info) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/package.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/package.py new file mode 100644 index 0000000..555215b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/package.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/firmware.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.utils import profiler + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.package_main import process + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import OPN_MOD_ARGS + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/package.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/package.html' + + +def run_module(): + module_args = dict( + name=dict( + type='list', required=True, elements='str', + description='Package or list of packages to process' + ), + action=dict( + type='str', required=True, + choices=['install', 'reinstall', 'remove', 'lock', 'unlock'] + ), + post_sleep=dict( + type='int', required=False, default=3, + description='The firewall needs some time to update package info' + ), + **OPN_MOD_ARGS + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + if module.params['profiling'] or module.params['debug']: + profiler( + check=process, kwargs=dict( + m=module, p=module.params, r=result, + ), + ) + + else: + process(m=module, p=module.params, r=result) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_address.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_address.py new file mode 100644 index 0000000..4dfcfc9 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_address.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_address import Address + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + address=dict( + type='str', required=True, aliasses=['from'], + description='Set a pattern to match line user@example.com or @example.com', + ), + to=dict( + type='list', required=False, elements='str', + description='Set here how to rewrite the Rewrite From pattern.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('to',)), + ], + ) + + module_wrapper(Address(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_domain.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_domain.py new file mode 100644 index 0000000..7905537 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_domain.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_domain import Domain + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + domainname=dict( + type='str', required=True, aliases=['name'], + description='Set the unique domain name to relay for.', + ), + destination=dict( + type='str', required=False, + description='Set the IP or FQDN to where to send the mails to. Empty means MX will be used. You can also ' + 'add custom ports via :225 or disable MX lookup via squared brackets.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Domain(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_general.py new file mode 100644 index 0000000..77dda7f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_general.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + myhostname=dict( + type='str', required=False, + description='The \'System Hostname\' parameter specifies the internet hostname of this mail system. ' + 'The default is to use the fully-qualified domain name from gethostname(). It is used as a ' + 'default value for many other configuration parameters.', + ), + mydomain=dict( + type='str', required=False, + description='The \'System Domain\' parameter specifies the local internet domain name. The default is to ' + 'use \'System Hostname\' minus the first component. It is used as a default value for many ' + 'other configuration parameters.', + ), + myorigin=dict( + type='str', required=False, + description='The \'System Origin\' parameter specifies the domain that locally-posted mail appears to ' + 'come from. The default is to append \'System Hostname\', which is fine for small sites.', + ), + inet_interfaces=dict( + type='list', required=False, default=['all'], + description='The \'Listen IPs\' parameter specifies a comma-separated list of IP addresses to listen to. ' + 'Default is to listen on all interfaces.', + ), + inet_port=dict( + type='int', required=False, default=25, + description='Port to listen on. Default is to listen on port 25.', + ), + ip_version=dict( + type='str', required=False, choices=['all', 'ipv4', 'ipv6'], default='all', + description='Choose which IP versions are allowed, defaults to all. One of: all, ipv4 or ipv6.', + ), + bind_address=dict( + type='str', required=False, + description='Specify the IPv4 address the server should bind to for outgoing connections. ' + 'In most cases empty is fine.', + ), + bind_address6=dict( + type='str', required=False, + description='Specify the IPv6 address the server should bind to for outgoing connections. ' + 'In most cases empty is fine.', + ), + mynetworks=dict( + type='list', required=False, elements='str', + default=['127.0.0.0/8', '[::ffff:127.0.0.0]/104', '[::1]/128'], + description='The \'Trusted Networks\' parameter specifies the list of trusted SMTP clients. ' + 'In particular, trusted SMTP clients are allowed to relay mail through Postfix. ' + 'Please use CIDR notation like 192.168.0.0/24 separated by spaces. IPv6 addresses ' + 'have to be in square brackets like [::1]/128.', + ), + banner=dict( + type='str', required=False, + description='The smtpd_banner parameter specifies the text that follows the 220 code in the SMTP ' + 'server\'s greeting banner. Default is "\'System Hostname\' ESMTP Postfix".', + ), + message_size_limit=dict( + type='int', required=False, default=51200000, + description='Set the max size for messages to accept, default is 51200000 Bytes which is 50MB. ' + 'Values must be entered in Bytes.', + ), + masquerade_domains=dict( + type='list', required=False, elements='str', default=[], + description='Masquerade internal domains to the outside. When you set example.com, the domain ' + 'host.internal.example.com will be rewritten to example.com when mail leaves the system.' + ), + tls_server_compatibility=dict( + type='str', required=False, choices=['modern', 'intermediate', 'old'], default='intermediate', + description='TLS version/cipher compatibility of the SMTP service. One of: modern, intermediate or old.' + 'Default to intermediate.', + ), + tls_client_compatibility=dict( + type='str', required=False, choices=['modern', 'intermediate', 'old'], default='intermediate', + description='TLS version/cipher compatibility of the SMTP Client. One of: modern, intermediate or old.' + 'Default to intermediate.', + ), + tlswrappermode=dict( + type='bool', required=False, default=0, + description='If enabled it allows you to use SMTPS.', + ), + certificate=dict( + type='str', required=False, + description='Choose the certificate to use when other servers want to do TLS with you.', + ), + ca=dict( + type='str', required=False, + description='Choose the Certificate Authority which signed your certificate.', + ), + smtpclient_security=dict( + type='str', required=False, choices=['none', 'may', 'encrypt', 'dane'], default='may', + description='\'none\' will disable TLS for sending mail. \'may\' will use TLS when offered. ' + '\'encrypt\' will enforce TLS on all connections. ' + '\'dane\' will enforce TLS if a TLSA-Record is published.', + ), + relayhost=dict( + type='str', required=False, aliases=['smarthost'], + description='Set the IP address or FQDN where all outgoing mails are sent to.', + ), + smtpauth_enabled=dict( + type='bool', required=False, default=False, + description='Check this to enable authentication against your Smarthost.', + ), + smtpauth_user=dict( + type='str', required=False, + description='The username to use for SMTP authentication.', + ), + smtpauth_password=dict( + type='str', required=False, no_log=True, + description='The password to use for SMTP authentication.', + ), + enforce_recipient_check=dict(type='bool', required=False, default=False), + extensive_helo_restrictions=dict(type='bool', required=False, default=False), + extensive_sender_restrictions=dict(type='bool', required=False, default=False), + reject_unknown_client_hostname=dict(type='bool', required=False, default=False), + reject_non_fqdn_helo_hostname=dict(type='bool', required=False, default=False), + reject_invalid_helo_hostname=dict(type='bool', required=False, default=False), + reject_unknown_helo_hostname=dict(type='bool', required=False, default=False), + reject_unauth_pipelining=dict(type='bool', required=False, default=True), + reject_unknown_sender_domain=dict( + type='bool', required=False, default=True, + description='This will reject mails from domains which do not exist.', + ), + reject_unknown_recipient_domain=dict(type='bool', required=False, default=True), + reject_non_fqdn_sender=dict( + type='bool', required=False, default=True, + description='For example senders without a domain or only a hostname.', + ), + reject_non_fqdn_recipient=dict( + type='bool', required=False, default=True, + description='For example recipients without a domain or only a hostname.', + ), + permit_sasl_authenticated=dict( + type='bool', required=False, default=True, + description='Allow SASL authenticated senders to relay. Will also enable smtpd_sasl_auth.', + ), + permit_tls_clientcerts=dict(type='bool', required=False, default=True), + permit_mynetworks=dict(type='bool', required=False, default=True), + reject_unauth_destination=dict(type='bool', required=False, default=True), + reject_unverified_recipient=dict( + type='bool', required=False, default=False, + description='Use Recipient Address Verification. Please keep in mind that this could put significant ' + 'load onto the next server.', + ), + delay_warning_time=dict( + type='int', required=False, default=0, + description='Time until we send a notification to the sender if mail is delayed (in hours). ' + '0 or empty to disable.', + ), + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('smtpauth_enabled', True, ('smtpauth_user', 'smtpauth_password')), + ], + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_headercheck.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_headercheck.py new file mode 100644 index 0000000..e05fc35 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_headercheck.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_headercheck import Headercheck + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + expression=dict( + type='str', required=True, + description='Set a regexp (POSIX regular expression) and an action to process like ' + '\'/^\\s*User-Agent/ IGNORE\'.', + ), + filter=dict( + type='str', required=True, choices=['WHILE_DELIVERING', 'WHILE_RECEIVING'], + description='Set when the header_check should be processed. One of: WHILE_DELIVERING or WHILE_RECEIVING.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Headercheck(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_recipient.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_recipient.py new file mode 100644 index 0000000..a96b208 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_recipient.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_recipient import Recipient + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + address=dict( + type='str', required=True, + description='Set the recipient address to match.', + ), + action=dict( + type='str', required=False, choices=['OK', 'REJECT'], + description='Set the action for this address. One of: OK or REJECT.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('action',)), + ], + ) + + module_wrapper(Recipient(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_recipientbcc.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_recipientbcc.py new file mode 100644 index 0000000..e90dca6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_recipientbcc.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_recipientbcc import RecipientBCC + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + address=dict( + type='str', required=True, aliasses=['from'], + description='Set a pattern to match like user@example.com', + ), + to=dict( + type='list', required=False, elements='str', + description='Set here the recipient address to send the mail as BCC.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('to',)), + ], + ) + + module_wrapper(RecipientBCC(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_sender.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_sender.py new file mode 100644 index 0000000..3572d80 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_sender.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_sender import Sender + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + address=dict( + type='str', required=True, + description='Set the sender address to match.', + ), + action=dict( + type='str', required=False, choices=['OK', 'REJECT'], + description='Set the action for this address. One of: OK or REJECT.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('action',)), + ], + ) + + module_wrapper(Sender(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_senderbcc.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_senderbcc.py new file mode 100644 index 0000000..e7da9ec --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_senderbcc.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_senderbcc import SenderBCC + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + address=dict( + type='str', required=True, aliasses=['from'], + description='Set a pattern to match like user@example.com', + ), + to=dict( + type='list', required=False, elements='str', + description='Set here the recipient address to send the mail as BCC.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('to',)), + ], + ) + + module_wrapper(SenderBCC(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_sendercanonical.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_sendercanonical.py new file mode 100644 index 0000000..c11720e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/postfix_sendercanonical.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.postfix_sendercanonical import \ + SenderCanonical + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/postfix.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/postfix.html' + + +def run_module(): + module_args = dict( + address=dict( + type='str', required=True, aliasses=['from'], + description='Set a pattern to match line user@example.com or @example.com', + ), + to=dict( + type='list', required=False, elements='str', + description='Set here how to rewrite the Rewrite From pattern.', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('state', 'present', ('to',)), + ], + ) + + module_wrapper(SenderCanonical(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/privilege.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/privilege.py new file mode 100644 index 0000000..55a2d71 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/privilege.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/auth.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.privilege import Privilege + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/auth.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/auth.html' + + +def run_module(): + module_args = dict( + id=dict( + type='str', required=True, aliases=['privilege', 'priv', 'p'], + description='Privilege ID', + ), + user=dict( + type='list', required=False, aliases=['u'], elements='str', default=[], + ), + group=dict( + type='list', required=False, aliases=['g'], elements='str', default=[], + ), + state=dict(type='str', required=False, choices=['present', 'absent', 'pure'], default='present'), + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Privilege(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/raw.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/raw.py new file mode 100644 index 0000000..dec5f5c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/raw.py @@ -0,0 +1,143 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# module to interact with system services + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + single_get, single_post, DEFAULT_TIMEOUT + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/general/raw.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/general/raw.html' + + +# pylint: disable=R0915 +def run_module(): + module_args = dict( + module=dict( + type='str', aliases=['m', 'mod'], default=None, + description='The API-module to target' + ), + controller=dict( + type='str',aliases=['co', 'cont'], + description='The API-controller to target' + ), + command=dict( + type='str', aliases=['c', 'cmd'], + description='The API-command to target' + ), + parameters=dict( + type='list', elements='str', aliases=['p', 'params'], + description='Optional: Parameters to send' + ), + url=dict( + type='str', aliases=['u'], default=None, + description='Alternative to module/controller/command' + ), + action=dict( + type='str', aliases=['a', 'method'], + choices=['get', 'post'], default='get', + ), + data=dict( + type='dict', default=None, aliases=['d'], + description='Optional: Supply data to send' + ), + headers=dict( + type='dict', default=None, aliases=['h'], + description='Optional: Supply headers to send' + ), + timeout=dict( + type='float', aliases=['t'], default=DEFAULT_TIMEOUT, + description='Timeout in seconds for request + response', + ), + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + response={}, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ('url', None, ('module', 'controller', 'command')), + ('module', 'None', 'url'), + ], + mutually_exclusive=[ + ('url', 'module'), + ], + ) + + p = module.params + m = p['module'] + c = p['controller'] + cmd = p['command'] + par = p['parameters'] + + if p['url'] is not None: + try: + m, c, cmd_params = p['url'].split('/', 2) + if cmd_params.find('/'): + cmd_params = cmd_params.split('/') + cmd = cmd_params[0] + par = cmd_params[1:] + + else: + cmd = cmd_params + par = None + + except ValueError: + module.fail_json( + "Provided URL has too few parts. " + "Example: '//(/)" + ) + + req = dict( + module=module, + cnf=dict( + module=m, + controller=c, + command=cmd, + params=par, + data=p['data'], + ), + timeout=p['timeout'], + ) + + if p['action'] == 'get': + result['response'] = single_get(**req) + + else: + if module.check_mode: + module.warn('Post actions are not performed in check-mode!') + + else: + result['changed'] = True + result['response'] = single_post(**req, headers=p['headers']) + + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/reload.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/reload.py new file mode 100644 index 0000000..59828a8 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/reload.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# module to reload running config +# pylint: disable=R0912,R0915,R0914 + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import OPN_MOD_ARGS + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/general/reload.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/general/reload.html' + + +def run_module(): + module_args = dict( + target=dict( + type='str', required=True, aliases=['tgt', 't'], + choices=[ + 'alias', + 'rule', + 'route', + 'gateway', + 'cron', + 'unbound', + 'syslog', + 'ipsec', + 'ipsec_legacy', + 'shaper', + 'monit', + 'wireguard', + 'interface_vlan', + 'interface_vxlan', + 'interface_vip', + 'interface_lagg', + 'frr', + 'webproxy', + 'bind', + 'ids', + 'openvpn', + 'dhcrelay', + 'dhcp', 'kea', + 'dnsmasq', + 'haproxy' + 'wazuh', + ], + description='What part of the running config should be reloaded' + ), + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + target = module.params['target'] + Target_Obj = None + + try: + # NOTE: dynamic imports not working as Ansible will not copy those modules to the temporary directory + # the module is executed in! + # see: ansible.executor.module_common.ModuleDepFinder (analyzing imports to know what dependencies to copy) + + if target == 'alias': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.alias import \ + Alias as Target_Obj + + elif target == 'rule': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.rule import \ + Rule as Target_Obj + + elif target == 'route': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.route import \ + Route as Target_Obj + + elif target == 'gateway': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.gateway import \ + Gw as Target_Obj + + elif target == 'cron': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.cron import \ + CronJob as Target_Obj + + elif target == 'unbound': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_host import \ + Host as Target_Obj + + elif target == 'syslog': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.syslog import \ + Syslog as Target_Obj + + elif target == 'ipsec': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_connection import \ + Connection as Target_Obj + + elif target == 'ipsec_legacy': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ipsec_cert import \ + KeyPair as Target_Obj + + elif target == 'shaper': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.shaper_pipe import \ + Pipe as Target_Obj + module.params['reset'] = False + + elif target == 'monit': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.monit_service import \ + Service as Target_Obj + + elif target == 'wireguard': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wireguard_server import \ + Server as Target_Obj + + elif target == 'interface_vlan': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vlan import \ + Vlan as Target_Obj + + elif target == 'interface_vxlan': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vxlan import \ + Vxlan as Target_Obj + + elif target == 'interface_vip': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_vip import \ + Vip as Target_Obj + + elif target == 'interface_lagg': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.interface_lagg import \ + Lagg as Target_Obj + + elif target == 'frr': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.frr_bgp_general import \ + General as Target_Obj + + elif target == 'webproxy': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_general import \ + General as Target_Obj + + elif target == 'bind': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.bind_domain import \ + Domain as Target_Obj + + elif target == 'ids': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.ids_general import \ + General as Target_Obj + + elif target == 'openvpn': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.openvpn_client import \ + Client as Target_Obj + + elif target == 'dhcrelay': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcrelay_relay import \ + DhcRelayRelay as Target_Obj + + elif target in ['dhcp', 'kea']: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dhcp_reservation_v4 import \ + ReservationV4 as Target_Obj + + elif target == 'wazuh': + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wazuh_agent import \ + WazuhAgent as Target_Obj + + elif target in ['dnsmasq']: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.dnsmasq_general import \ + General as Target_Obj + + elif target in ['haproxy']: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.haproxy_general_settings import \ + HaproxyGeneralSettings as Target_Obj + + except MODULE_EXCEPTIONS: + module_dependency_error() + + if Target_Obj is not None: + target_inst = Target_Obj(module=module, result=result) + + result['changed'] = True + if not module.check_mode: + target_inst.reload() + + if hasattr(target_inst, 's'): + target_inst.s.close() + + else: + module.fail_json(f"Got unsupported target: '{target}'") + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/route.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/route.py new file mode 100644 index 0000000..9760e39 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/route.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/routes.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.route import Route + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/route.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/route.html' + + +def run_module(): + module_args = dict( + gateway=dict( + type='str', required=True, aliases=['gw'], + description='Specify a valid existing gateway matching the networks ip protocol' + ), + network=dict( + type='str', required=True, aliases=['nw', 'net'], + description='Specify a valid network matching the gateways ip protocol' + ), + description=dict(type='str', required=False, aliases=['desc']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured routes with the running config - ' + "if any of those fields are changed, the module will think it's a new route", + choices=['network', 'gateway', 'description'], + default=['network', 'gateway'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Route(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule.py new file mode 100644 index 0000000..470dbfc --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/firewall.html + +from ansible.module_utils.basic import AnsibleModule + + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import \ + module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.rule import RULE_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.rule import Rule + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/rule.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/rule.html' + + +def run_module(): + module_args = dict( + **RULE_MOD_ARGS, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + }, + ) + + module_wrapper(Rule(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_interface_group.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_interface_group.py new file mode 100644 index 0000000..e36d09b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_interface_group.py @@ -0,0 +1,77 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/firewall.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.rule_interface_group import Group + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/rule_interface_group.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/rule_interface_group.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['ifname'], + description='Name of the interface group. Only texts containing letters, ' + 'digits and underscores with a maximum length of 15 characters ' + 'are allowed and the name may not end with a digit.', + ), + members=dict( + type='list', elements='str', required=False, aliases=['ints', 'interfaces'], + description='Member interfaces - you must provide the network ' + "port as shown in 'Interfaces - Assignments - Network port'" + ), + gui_group=dict( + type='bool', required=False, aliases=['gui'], default=True, + description='Grouping these members in the interfaces menu section' + ), + sequence=dict( + type='int', required=False, default=0, aliases=['seq'], + description='Priority sequence used in sorting the groups ' + ), + description=dict(type='str', required=False, aliases=['desc']), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Group(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_multi.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_multi.py new file mode 100644 index 0000000..8a84468 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_multi.py @@ -0,0 +1,78 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/firewall.html + +from ansible.module_utils.basic import AnsibleModule + + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import \ + module_multi_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.multi import \ + build_multi_mod_args + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.rule import \ + RULE_MOD_ARGS, RULE_MATCH_FIELDS_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.rule import Rule + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/rule.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/rule.html' + + +def run_module(): + entry_multi_args = build_multi_mod_args( + mod_args=RULE_MOD_ARGS, + aliases=['rules'], + ) + + module_args = dict( + **entry_multi_args, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + **RULE_MATCH_FIELDS_ARG, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_one_of=[ + ('multi', 'multi_purge', 'multi_control.purge_all'), + ], + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + }, + ) + + module_multi_wrapper( + module=module, + result=result, + obj=Rule, + kind='rule', + entry_args=entry_multi_args, + ) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_purge.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_purge.py new file mode 100644 index 0000000..9720a86 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/rule_purge.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/firewall.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.legacy_multi import \ + PURGE_MOD_ARGS, INFO_MOD_ARG, RULE_MOD_ARG_KEY_FIELD + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.rule import \ + RULE_MATCH_FIELDS_ARG + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/rule_multi.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/rule_multi.html' + + +def run_module(): + module_args = dict( + rules=dict( + type='dict', required=False, default={}, + description='Configured rules - compared against existing ones' + ), + fail_all=dict( + type='bool', required=False, default=False, aliases=['fail'], + description='Fail module if single rule fails to be purged.' + ), + **PURGE_MOD_ARGS, + **INFO_MOD_ARG, + **RULE_MOD_ARG_KEY_FIELD, + **RULE_MATCH_FIELDS_ARG, + **OPN_MOD_ARGS, + ) + + AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ).fail_json('This module was deprecated in favor of: https://ansible-opnsense.oxl.app/modules/1_multi.html') + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/savepoint.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/savepoint.py new file mode 100644 index 0000000..257d41c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/savepoint.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/firewall.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.savepoint import SavePoint + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/savepoint.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/savepoint.html' + + +def run_module(): + module_args = dict( + action=dict( + type='str', required=False, default='create', + choices=['create', 'revert', 'apply', 'cancel_rollback', 'cancel'], + ), + revision=dict( + type='str', required=False, + description='Savepoint revision to apply, revert or cancel_rollback' + ), + controller=dict( + type='str', required=False, default='filter', description='Target API controller', + choices=['source_nat', 'filter', 'one_to_one'] + ), + api_module=dict(type='str', required=False, default='firewall', choices=['firewall']), + **OPN_MOD_ARGS + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + }, + revision='', + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + sp = SavePoint(module=module, result=result) + + if module.params['action'] == 'create': + result['revision'] = sp.create() + + else: + if module.params['revision'] is None: + module.fail_json('You need to provide a revision to execute this action!') + + if module.params['action'] == 'apply': + sp.apply() + + elif module.params['action'] == 'revert': + sp.revert() + + else: + sp.cancel_rollback() + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/service.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/service.py new file mode 100644 index 0000000..428d518 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/service.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# module to interact with system services + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import \ + single_get, single_post + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/general/service.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/general/service.html' + +# c = api-module, m = custom action-mapping, a = limited actions +SERVICES = { + # core api + 'captive_portal': {'c': 'captiveportal', 'a': ['reload']}, + 'cron': {'a': ['reload']}, + 'ipsec_legacy': {'c': 'legacy_subsystem', 'a': ['reload'], 'm': {'reload': 'applyConfig'}}, + 'ipsec': {}, 'monit': {}, 'syslog': {}, + 'shaper': { + 'c': 'trafficshaper', 'a': ['reload', 'status', 'restart'], + 'm': {'restart': 'flushreload', 'status': 'statistics'} + }, + 'openvpn': { + 'c': 'openvpn', 'a': ['stop', 'status', 'start', 'reload', 'restart'], + 'm': { + 'start': 'startService', 'reload': 'reconfigure', 'restart': 'restartService', + 'status': 'searchSessions', 'stop': 'stopService', + }, + }, + # note: these would support more actions: + 'ids': {}, 'proxy': {}, 'unbound': {}, 'kea': {}, 'dnsmasq': {}, + # plugins + 'ftp_proxy': {'c': 'ftpproxy'}, + 'iperf': {'a': ['reload', 'status', 'start', 'restart']}, + 'mdns_repeater': {'c': 'mdnsrepeater', 'a': ['stop', 'status', 'start', 'restart']}, + 'munin_node': {'c': 'muninnode'}, + 'node_exporter': {'c': 'nodeexporter'}, + 'puppet_agent': {'c': 'puppetagent'}, + 'qemu_guest_agent': {'c': 'qemuguestagent'}, + 'frr': {'c': 'quagga'}, + 'radsec_proxy': {'c': 'radsecproxy'}, + 'zabbix_agent': {'c': 'zabbixagent'}, + 'zabbix_proxy': {'c': 'zabbixproxy'}, + 'apcupsd': {}, 'bind': {}, 'chrony': {}, 'cicap': {}, 'collectd': {}, + 'dyndns': {}, 'fetchmail': {}, 'freeradius': {}, 'haproxy': {}, 'maltrail': {}, + 'netdata': {}, 'netsnmp': {}, 'nrpe': {}, 'nut': {}, 'openconnect': {}, 'proxysso': {}, + 'rspamd': {}, 'shadowsocks': {}, 'softether': {}, 'sslh': {}, 'stunnel': {}, 'tayga': {}, + 'telegraf': {}, 'tftp': {}, 'tinc': {}, 'wazuh_agent': {'c': 'wazuhagent'}, 'wireguard': {}, + # note: these would support more actions: + 'acme_client': {'c': 'acmeclient'}, + 'crowdsec': {'a': ['reload', 'status']}, + 'dns_crypt_proxy': {'c': 'dnscryptproxy'}, + 'udp_broadcast_relay': {'c': 'udpbroadcastrelay'}, + 'clamav': {}, 'hwprobe': {}, 'lldpd': {}, 'nginx': {}, 'ntopng': {}, 'postfix': {}, 'redis': {}, + 'relayd': {}, 'siproxd': {}, 'vnstat': {}, 'tor': {}, +} + +ACTION_MAPPING = {'reload': 'reconfigure'} +API_CONTROLLER = 'service' + + +# pylint: disable=R0915 +def run_module(): + service_choices = list(SERVICES.keys()) + service_choices.sort() + + module_args = dict( + name=dict( + type='str', required=True, aliases=['service', 'svc', 'target', 'n'], + choices=service_choices, + description='What service to interact with' + ), + action=dict( + type='str', required=True, aliases=['do', 'a'], + choices=['reload', 'restart', 'start', 'status', 'stop'], + description='What action to execute. Some services may not support all of these actions - ' + 'the module will inform you in that case' + ), + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + name = module.params['name'] + action = module.params['action'] + service = SERVICES[name] + + if 'a' in service and action not in service['a']: + module.fail_json( + f"Service '{name}' does not support the " + f"provided action '{action}'! " + f"Supported ones are: {service['a']}" + ) + + # translate actions to api-commands + # pylint: disable=R1715 + if 'm' in service and action in service['m']: + action = service['m'][action] + + elif action in ACTION_MAPPING: + action = ACTION_MAPPING[action] + + result['executed'] = action + + # get api-module + if 'c' in service: + api_module = service['c'] + + else: + api_module = name + + # pull status or execute action + if module.params['action'] == 'status': + info = single_get( + module=module, + cnf={ + 'module': api_module, + 'controller': API_CONTROLLER, + 'command': action, + } + ) + + if 'response' in info: + info = info['response'] + + if isinstance(info, str): + info = info.strip() + + result['data'] = info + + else: + result['changed'] = True + + if not module.check_mode: + single_post( + module=module, + cnf={ + 'module': api_module, + 'controller': API_CONTROLLER, + 'command': action, + } + ) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_pipe.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_pipe.py new file mode 100644 index 0000000..3ef5696 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_pipe.py @@ -0,0 +1,91 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/trafficshaper.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.shaper_pipe import Pipe + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/shaper.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/shaper.html' + + +def run_module(): + module_args = dict( + # id=dict(type='int', required=True, aliases=['number']), # ignored and set automatically + bandwidth=dict( + type='int', required=False, aliases=['bw'] + ), + bandwidth_metric=dict( + type='str', required=False, default='Mbit', aliases=['bw_metric'], + choices=['bit', 'Kbit', 'Mbit', 'Gbit'], + ), + queue=dict(type='str', required=False), + mask=dict( + type='str', required=False, default='none', choices=['none', 'src-ip', 'dst-ip'] + ), + buckets=dict(type='str', required=False), + scheduler=dict( + type='str', required=False, + choices=['fifo', 'rr', 'qfq', 'fq_codel', 'fq_pie'] + ), + pie_enable=dict(type='bool', required=False, default=False, aliases=['pie']), + codel_enable=dict(type='bool', required=False, default=False, aliases=['codel']), + codel_ecn_enable=dict(type='bool', required=False, default=False, aliases=['codel_ecn']), + codel_target=dict(type='str', required=False), + codel_interval=dict(type='int', required=False), + fqcodel_quantum=dict(type='str', required=False), + fqcodel_limit=dict(type='str', required=False), + fqcodel_flows=dict(type='str', required=False), + delay=dict(type='str', required=False), + description=dict(type='str', required=True, aliases=['desc']), + reset=dict( + type='bool', required=False, default=False, aliases=['flush'], + description='If the running config should be flushed and reloaded on change - ' + 'will take some time. This might have impact on other services using ' + 'the same technology underneath (such as Captive portal)' + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + + module_wrapper(Pipe(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_queue.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_queue.py new file mode 100644 index 0000000..5689697 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_queue.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/trafficshaper.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.shaper_queue import Queue + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/shaper.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/shaper.html' + + +def run_module(): + module_args = dict( + pipe=dict(type='str', required=False), + mask=dict( + type='str', required=False, default='none', choices=['none', 'src-ip', 'dst-ip'] + ), + weight=dict(type='str', required=False), + buckets=dict(type='str', required=False), + pie_enable=dict(type='bool', required=False, default=False, aliases=['pie']), + codel_enable=dict(type='bool', required=False, default=False, aliases=['codel']), + codel_ecn_enable=dict(type='bool', required=False, default=False, aliases=['codel_ecn']), + codel_target=dict(type='str', required=False), + codel_interval=dict(type='int', required=False), + description=dict(type='str', required=True, aliases=['desc']), + reset=dict( + type='bool', required=False, default=False, aliases=['flush'], + description='If the running config should be flushed and reloaded on change - ' + 'will take some time. This might have impact on other services using ' + 'the same technology underneath (such as Captive portal)' + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Queue(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_rule.py new file mode 100644 index 0000000..ac2aada --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/shaper_rule.py @@ -0,0 +1,122 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/trafficshaper.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.rule import \ + RULE_MOD_ARG_ALIASES + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.shaper_rule import Rule + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/shaper.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/shaper.html' + + +def run_module(): + module_args = dict( + target_pipe=dict(type='str', required=False, aliases=['pipe']), + target_queue=dict(type='str', required=False, aliases=['queue']), + sequence=dict( + type='int', required=False, default=1, aliases=RULE_MOD_ARG_ALIASES['sequence'] + ), + interface=dict( + type='str', required=False, default='lan', aliases=RULE_MOD_ARG_ALIASES['interface'], + description='Matching packets traveling to/from interface', + ), + interface2=dict( + type='str', required=False, aliases=['int2', 'i2'], + description='Secondary interface, matches packets traveling to/from interface ' + '(1) to/from interface (2). can be combined with direction.', + ), + protocol=dict( + type='str', required=False, default='ip', aliases=RULE_MOD_ARG_ALIASES['protocol'], + description="Protocol like 'ip', 'ipv4', 'tcp', 'udp' and so on." + ), + max_packet_length=dict( + type='int', required=False, aliases=['max_packet_len', 'packet_len', 'iplen'], + ), + source_invert=dict( + type='bool', required=False, default=False, + aliases=RULE_MOD_ARG_ALIASES['source_invert'], + ), + source_net=dict( + type='list', elements='str', required=False, default='any', aliases=RULE_MOD_ARG_ALIASES['source_net'], + description="Source ip or network, examples 10.0.0.0/24, 10.0.0.1" + ), + source_port=dict( + type='str', required=False, default='any', aliases=RULE_MOD_ARG_ALIASES['source_port'], + ), + destination_invert=dict( + type='bool', required=False, default=False, + aliases=RULE_MOD_ARG_ALIASES['destination_invert'], + ), + destination_net=dict( + type='list', elements='str', required=False, default='any', aliases=RULE_MOD_ARG_ALIASES['destination_net'], + description='Destination ip or network, examples 10.0.0.0/24, 10.0.0.1' + ), + destination_port=dict( + type='str', required=False, default='any', + aliases=RULE_MOD_ARG_ALIASES['destination_port'], + ), + dscp=dict( + type='list', required=False, elements='str', default=[], + description='One or multiple DSCP values', + choices=[ + 'be', 'ef', 'af11', 'af12', 'af13', 'af21', 'af22', 'af23', 'af31', 'af32', 'af33', + 'af41', 'af42', 'af43', 'cs1', 'cs2', 'cs3', 'cs4', 'cs5', 'cs6', 'cs7', + ] + ), + direction=dict( + type='str', required=False, aliases=RULE_MOD_ARG_ALIASES['direction'], + choices=['in', 'out'], description='Leave empty for both' + ), + description=dict(type='str', required=True, aliases=['desc']), + reset=dict( + type='bool', required=False, default=False, aliases=['flush'], + description='If the running config should be flushed and reloaded on change - ' + 'will take some time. This might have impact on other services using ' + 'the same technology underneath (such as Captive portal)' + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Rule(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/snapshot.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/snapshot.py new file mode 100644 index 0000000..ca2042b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/snapshot.py @@ -0,0 +1,58 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# template to be copied to implement new modules + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_ONLY_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.snapshot import Snapshot + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/snapshot.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/snapshot.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + activate=dict(type='bool', required=False, default=False), + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Snapshot(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/syslog.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/syslog.py new file mode 100644 index 0000000..6addbe5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/syslog.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/syslog.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.syslog import Syslog + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/syslog.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/syslog.html' + + +def run_module(): + module_args = dict( + target=dict( + type='str', required=True, aliases=['hostname', 'tgt', 'server', 'srv'], + description='Server to forward the logs to' + ), + port=dict(type='int', required=False, default=514, aliases=['p']), + transport=dict( + type='str', required=False, default='udp4', aliases=['trans', 't'], + choices=['udp4', 'tcp4', 'udp6', 'tcp6', 'tls4', 'tls6'], + ), + level=dict( + type='list', required=False, aliases=['lv', 'lvl'], elements='str', + default=['info', 'notice', 'warn', 'err', 'crit', 'alert', 'emerg'], + choices=['debug', 'info', 'notice', 'warn', 'err', 'crit', 'alert', 'emerg'], + ), + program=dict( + type='list', required=False, aliases=['prog'], default=[], elements='str', + description='Limit applications to send logs from' + ), + facility=dict( + type='list', required=False, aliases=['fac'], default=[], elements='str', + choices=[ + 'kern', 'user', 'mail', 'daemon', 'auth', 'syslog', 'lpr', 'news', 'uucp', 'cron', 'authpriv', + 'ftp', 'ntp', 'security', 'console', 'local0', 'local1', 'local2', 'local3', 'local4', + 'local5', 'local6', 'local7', + ], + ), + certificate=dict(type='str', required=False, aliases=['cert']), + rfc5424=dict(type='bool', required=False, default=False), # not getting current value from response + description=dict(type='str', required=False, aliases=['desc']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured syslog-destinations with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=[ + 'target', 'transport', 'facility', 'program', 'level', + 'port', 'description', + ], + default=['target', 'facility', 'program'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Syslog(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/system.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/system.py new file mode 100644 index 0000000..80b047b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/system.py @@ -0,0 +1,96 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/firmware.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.system import wait_for_response, \ + wait_for_update, get_upgrade_status + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/package.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/package.html' + + +def run_module(): + module_args = dict( + action=dict( + type='str', required=True, + choices=['poweroff', 'reboot', 'update', 'upgrade', 'audit'], + description="WARNING: Only use the 'upgrade' option in test-environments. " + "In production you should use the WebUI to upgrade!" + ), + wait=dict(type='bool', required=False, default=True), + wait_timeout=dict(type='int', required=False, default=90), + poll_interval=dict(type='int', required=False, default=2), + force_upgrade=dict(type='bool', required=False, default=False), + **OPN_MOD_ARGS + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = { + 'changed': True, + 'failed': False, + 'timeout_exceeded': False, + } + + if module.params['action'] == 'upgrade' and not module.params['force_upgrade']: + module.fail_json( + "If you really want to perform an upgrade - you need to additionally supply the 'force_upgrade' argument. " + "WARNING: Using the 'upgrade' action is only recommended for test-environments. " + "In production you should use the WebUI to upgrade!" + ) + + if not module.check_mode: + with Session(module=module) as s: + upgrade_status = get_upgrade_status(s) + if upgrade_status['status'] not in ['done', 'error']: + module.fail_json( + f'System may be upgrading! System-actions are currently blocked! Details: {upgrade_status}' + ) + + s.post({ + 'command': module.params['action'], + 'module': 'core', + 'controller': 'firmware', + }) + + if module.params['wait']: + if module.params['debug']: + module.warn(f"Waiting for firewall to complete '{module.params['action']}'!") + + try: + if module.params['action'] in ['upgrade', 'update']: + result['failed'] = not wait_for_update(module=module, s=s) + + elif module.params['action'] == 'reboot': + result['failed'] = not wait_for_response(module=module) + + except TimeoutError: + result['timeout_exceeded'] = True + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_acl.py new file mode 100644 index 0000000..e10cc1a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_acl.py @@ -0,0 +1,83 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/unbound.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_acl import Acl + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/unbound_acl.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/unbound_acl.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['n'], + decription='Provide an access list name', + ), + action=dict( + type='str', required=False, default='allow', + choices=['allow', 'deny', 'refuse', 'allow_snoop', 'deny_non_local', 'refuse_non_local'], + decription='Choose what to do with DNS requests that match the criteria specified below: ' + '* DENY: This action stops queries from hosts within the netblock defined below. ' + '* REFUSE: This action also stops queries from hosts within the netblock defined below, ' + 'but sends a DNS rcode REFUSED error message back to the client. ' + '* ALLOW: This action allows queries from hosts within the netblock defined below. ' + '* ALLOW SNOOP: This action allows recursive and nonrecursive access from hosts within ' + 'the netblock defined below. ' + 'Used for cache snooping and ideally should only be configured for your administrative host. ' + '* DENY NON-LOCAL: Allow only authoritative local-data queries from hosts within the netblock ' + 'defined below. Messages that are disallowed are dropped. ' + '* REFUSE NON-LOCAL: Allow only authoritative local-data queries from hosts within the ' + 'netblock defined below. ' + 'Sends a DNS rcode REFUSED error message back to the client for messages that are disallowed.', + ), + networks=dict( + type='list', elements='str', required=False, aliases=['nets'], + decription='List of networks in CIDR notation to apply this ACL on. For example: 192.168.1.0/24', + ), + description=dict(type='str', required=False, aliases=['desc']), + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Acl(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_dnsbl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_dnsbl.py new file mode 100644 index 0000000..36b66f6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_dnsbl.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/unbound.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_dnsbl import DnsBL + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/unbound_general.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/unbound_general.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['description', 'desc'], + description='Unique name to identify the entry', + ), + providers=dict( + type='list', elements='str', required=False, default=[], + aliases=['type', 'dnsbl', 'bl'], + description='Select which kind of DNSBL you want to use.' + ), + download_urls=dict( + type='list', elements='str', required=False, default=[], + aliases=['download', 'lists'], + description='List of URLs/domains from where blocklist will be downloaded' + ), + domains_allow=dict( + type='list', elements='str', required=False, default=[], aliases=['allowlists'], + description='List of domains to allow. You can use regular expressions. ' + 'This allow list only applies to blocklist matches on items in this policy' + ), + domains_block=dict( + type='list', elements='str', required=False, default=[], aliases=['blocklists'], + description='List of domains to blocklist. Only exact matches are supported' + ), + wildcard_domains_block=dict( + type='list', elements='str', required=False, default=[], + aliases=['wildcards_block', 'wildcard_domains', 'wildcards'], + description='List of wildcard domains to blocklist. All subdomains of the given domain will be blocked. ' + 'Blocking first-level domains is not supported' + ), + source_networks=dict( + type='list', elements='str', required=False, default=[], + aliases=['networks', 'source_nets', 'src_nets'], + description='Source networks to apply policy on. ' + 'Examples are 192.168.1.0/24 or 192.168.1.1. Leave empty to apply on everything. ' + 'All specified networks should use the same protocol family and ' + 'have equal sizes to avoid priority issue' + ), + cache_ttl=dict( + type='int', required=False, default=72_000, aliases=['ttl'], + description="TTL-seconds for the blocklists cache. " + "Remote blocklists don't usually update more often than once a day. " + "Therefore, when blocklists are downloaded, they are cached locally to prevent " + "unnecessary fetches over the internet. You can change this behavior here if you know " + "the remote files rotate faster than this" + ), + nxdomain_address=dict( + type='str', required=False, aliases=['address', 'redirect_to'], + description='Destination ip address for entries in the blocklist (leave empty to use default: 0.0.0.0). ' + 'Not used when "Return NXDOMAIN" is checked' + ), + nxdomain=dict( + type='bool', required=False, default=False, + description='Use the DNS response code NXDOMAIN instead of a destination address' + ), + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(DnsBL(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_dot.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_dot.py new file mode 100644 index 0000000..5e55c1d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_dot.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/unbound.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_dot import DnsOverTls + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/unbound_dot.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/unbound_dot.html' + + +def run_module(): + module_args = dict( + domain=dict( + type='str', required=False, aliases=['dom', 'd'], + description='Provide a domain to limit the DNS-over-TLS to or leave empty to act as a catch-all' + ), + target=dict( + type='str', required=True, aliases=['tgt', 'server', 'srv'], + description='Server to forward the dns queries to' + ), + port=dict( + type='int', required=False, default=53, aliases=['p'], + description='DNS port of the target server' + ), + verify=dict( + type='str', required=False, aliases=['common_name', 'cn', 'hostname'], + description='Verify if CN in certificate matches this value, if not set - ' + 'certificate verification will not be performed!' + ), + type=dict(type='str', required=False, choices=['dot'], default='dot'), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(DnsOverTls(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_forward.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_forward.py new file mode 100644 index 0000000..8236c4e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_forward.py @@ -0,0 +1,78 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/unbound.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_forward import Forward + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/unbound_forwarding.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/unbound_forwarding.html' + + +def run_module(): + module_args = dict( + domain=dict( + type='str', required=False, aliases=['dom', 'd'], + description='Domain of the host. All queries for this domain will be forwarded to the nameserver ' + 'specified. Leave empty to catch all queries and forward them to the nameserver' + ), + target=dict( + type='str', required=True, aliases=['tgt', 'server', 'srv'], + description='Server to forward the dns queries to' + ), + port=dict( + type='int', required=False, default=53, aliases=['p'], + description='DNS port of the target server' + ), + type=dict(type='str', required=False, choices=['forward'], default='forward'), + forward_tcp=dict( + type='bool', required=False, default=False, aliases=['forward_tcp_upstream', 'fwd_tcp'], + description='Upstream queries use TCP only for transport regardless of global flag tcp-upstream. ' + 'Please note this setting applies to the domain, so when multiple forwarders are ' + 'defined for the same domain, all are assumed to use tcp only.' + ), + description=dict(type='str', required=False, aliases=['desc']), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Forward(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_general.py new file mode 100644 index 0000000..c58e2da --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_general.py @@ -0,0 +1,132 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/unbound.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/unbound_general.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/unbound_general.html' + + +def run_module(): + module_args = dict( + port=dict( + type='int', required=False, default=53, + description='The TCP/UDP port used for responding to DNS queries' + ), + interfaces=dict( + type='list', elements='str', required=False, default=[], + description='The interface(s) used for responding to queries from clients' + ), + dnssec=dict( + type='bool', required=False, default=False, + description='Whether DNSSEC is enabled' + ), + dns64=dict( + type='bool', required=False, default=False, + description='Whether Unbound will synthesize AAAA records from A records if no ' + 'actual AAAA records are present' + ), + # IPv6 netmask + dns64_prefix=dict( + type='str', required=False, default='64:ff9b::/96', + description='The DNS64 prefix' + ), + aaaa_only_mode=dict( + type='bool', required=False, default=False, + description='Whether Unbound will remove all A records from the answer section ' + 'of all responses' + ), + register_dhcp_leases=dict( + type='bool', required=False, default=False, + description='Whether machines that specify their hostname when requesting a ' + 'DHCP lease will be registered in Unbound' + ), + dhcp_domain=dict( + type='str', required=False, + description='The default domain name to use for DHCP lease registration' + ), + register_dhcp_static_mappings=dict( + type='bool', required=False, default=False, + description='Whether DHCP static mappings will be registered in Unbound' + ), + register_ipv6_link_local=dict( + type='bool', required=False, default=True, + description='Whether IPv6 link-local addresses will be registered in Unbound' + ), + register_system_records=dict( + type='bool', required=False, default=True, + description='Whether A/AAAA records for the configured listen interfaces ' + 'will be generated' + ), + txt_records=dict( + type='bool', required=False, default=False, aliases=['txt'], + description='Whether descriptions associated with Host entries and DHCP Static ' + 'mappings will create a corresponding TXT record' + ), + flush_dns_cache=dict( + type='bool', required=False, default=False, + description='Whether the DNS cache will be flushed during each daemon reload' + ), + local_zone_type=dict( + type='str', required=False, default='transparent', choices=[ + 'transparent', 'always_nxdomain', 'always_refuse', 'always_transparent', 'deny', 'inform', + 'inform_deny', 'nodefault', 'refuse', 'static', 'typetransparent', + ], + description='The local zone type used for the system domain' + ), + outgoing_interfaces=dict( + type='list', elements='str', required=False, default=[], + description='The interface(s) that Unbound will use to send queries to ' + 'authoritative servers and receive their replies' + ), + wpad=dict( + type='bool', required=False, default=False, + description='Whether CNAME records for the WPAD host of all configured domains ' + 'will be automatically added as well as overrides for TXT records for domains' + ), + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_host.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_host.py new file mode 100644 index 0000000..3845828 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_host.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/unbound.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_host import Host + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/unbound_host.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/unbound_host.html' + + +def run_module(): + module_args = dict( + hostname=dict(type='str', required=True, aliases=['host', 'h']), + domain=dict(type='str', required=True, aliases=['dom', 'd']), + record_type=dict( + type='str', required=False, aliases=['type', 'rr', 'rt'], + choices=['A', 'AAAA', 'MX'], default='A', + ), + value=dict(type='str', required=False, aliases=['server', 'srv', 'mx']), + prio=dict( + type='int', required=False, aliases=['mxprio'], default=10, + description='Priority that is only used for MX record types' + ), + description=dict(type='str', required=False, aliases=['desc']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured host-overrides with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=[ + 'hostname', 'domain', 'record_type', 'value', + 'prio', 'description' + ], + default=['hostname', 'domain', 'record_type', 'value', 'prio'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Host(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_host_alias.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_host_alias.py new file mode 100644 index 0000000..e37d8f2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/unbound_host_alias.py @@ -0,0 +1,68 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/unbound.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.unbound_host_alias import Alias + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/unbound_host_alias.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/unbound_host_alias.html' + + +def run_module(): + module_args = dict( + alias=dict(type='str', required=True, aliases=['hostname']), + domain=dict(type='str', required=True, aliases=['dom', 'd']), + target=dict(type='str', required=False, aliases=['tgt', 'host']), + description=dict(type='str', required=False, aliases=['desc']), + match_fields=dict( + type='list', required=False, elements='str', + description='Fields that are used to match configured override-alias with the running config - ' + "if any of those fields are changed, the module will think it's a new entry", + choices=['hostname', 'domain', 'alias', 'description'], + default=['alias', 'domain'], + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Alias(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/user.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/user.py new file mode 100644 index 0000000..30d8f23 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/user.py @@ -0,0 +1,104 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/core/auth.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.user import User + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/auth.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/auth.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, aliases=['n'], + description='User name', + ), + expires=dict( + type='str', required=False, + description='Expiration date', + ), + authorized_keys=dict( + type='str', required=False, + description='SSH authorized keys', + ), + shell=dict( + type='str', required=False, choises=['/bin/csh', '/bin/sh', '/bin/tcsh'], + description='Login shell', + ), + password=dict(type='str', required=False, no_log=True), + update_password=dict( + type='str', required=False, choices=['always', 'on_create'], default='always', + description='Update the password `always` or only `on_create`.', + ), + scrambled_password=dict( + type='bool', required=False, + description='Generate a scrambled password to prevent local database logins for this user', + ), + landing_page=dict( + type='str', required=False, + description='Preferred landing page after login or authentication failure', + ), + comment=dict( + type='str', required=False, + description='User comment, for your own information only', + ), + email=dict( + type='str', required=False, + description='Users e-mail address, for your own information only', + ), + language=dict(type='str', required=False), + description=dict( + type='str', required=False, aliases=['desc', 'full_name'], + description='Full name of the user' + ), + membership=dict( + type='list', required=False, aliases=['group', 'm', 'g'], elements='str', + ), + privilege=dict( + type='list', required=False, aliases=['priv', 'p'], elements='str', + ), + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(User(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wazuh_agent.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wazuh_agent.py new file mode 100644 index 0000000..9523fc5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wazuh_agent.py @@ -0,0 +1,126 @@ +#!/usr/bin/python + +# Copyright: (C) 2025, MaximeWewer +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wazuh_agent import WazuhAgent + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/wazuh_agent.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/wazuh_agent.html' + + +def run_module(): + module_args = dict( + # General settings + server_address=dict( + type='str', required=False, aliases=['server'], + description='Specifies the IP address or the hostname of the Wazuh manager.' + ), + agent_name=dict( + type='str', required=False, default='', + description='Specifies the hostname of this agent.' + ), + protocol=dict( + type='str', required=False, default='tcp', + choices=['tcp', 'udp'], + description='Specifies the transport protocol to use.' + ), + port=dict( + type='int', required=False, default=1514, + description='Specifies the port to use for communicating with the Wazuh manager.' + ), + debug_level=dict( + type='int', required=False, default=0, + description='Debug level for this agents services.' + ), + + # Authentication settings + auth_password=dict( + type='str', required=False, no_log=True, + description='Password to use in authd.pass file.' + ), + auth_port=dict( + type='int', required=False, default=1515, + description='Specifies the port to use for communicating with the Wazuh manager during enrollment.' + ), + + # Log collector settings + remote_commands=dict( + type='bool', required=False, default=True, + description='Enable remote commands from the log collector' + ), + syslog_programs=dict( + type='list', required=False, elements='str', default=[], + description='Choose which applications to forward to Wazuh.' + ), + suricata_eve_log=dict( + type='bool', required=False, default=True, + description='Send events from the intrusion detection engine to Wazuh' + ), + + # Module enablers + rootcheck_enabled=dict( + type='bool', required=False, default=True, + description='Enable policy monitoring and anomaly detection' + ), + syscollector_enabled=dict( + type='bool', required=False, default=True, + description='Enable syscollector' + ), + syscheck_enabled=dict( + type='bool', required=False, default=True, + description='Enable file integrity monitoring' + ), + active_response_enabled=dict( + type='bool', required=False, default=True, + description='Enable Active response' + ), + active_response_remote_commands=dict( + type='bool', required=False, default=True, + description='Toggles whether Command Module should accept commands' + ), + active_response_fw_alias_ignore=dict( + type='list', required=False, elements='str', default=[], + description='Select an alias from which items should be ignored when dropping IP addresses' + ), + + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(WazuhAgent(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_acl.py new file mode 100644 index 0000000..8991bf0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_acl.py @@ -0,0 +1,130 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_acl import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + allow=dict( + type='list', elements='str', required=False, default=[], + aliases=['allow_subnets', 'subnets'], + description='IPs and Subnets you want to allow access to the ' + 'proxy server' + ), + exclude=dict( + type='list', elements='str', required=False, default=[], + aliases=['unrestricted', 'ignore'], + description='IPs and Subnets you want to bypass the proxy server' + ), + banned=dict( + type='list', elements='str', required=False, default=[], + aliases=['blocked', 'block', 'ban'], + description='IPs and Subnets you want to deny access to the ' + 'proxy server' + ), + exclude_domains=dict( + type='list', elements='str', required=False, default=[], + aliases=['safe_list', 'whitelist'], + description='Whitelist destination domains. You may use a regular expression, use a ' + 'comma or press Enter for new item. Examples: "mydomain.com" matches on ' + '"*.mydomain.com"; "^https?:\\/\\/([a-zA-Z]+)\\.mydomain\\." matches on ' + '"http(s)://textONLY.mydomain.*"; "\\.gif$" matches on "\\*.gif" but not on ' + '"\\*.gif\\test"; "\\[0-9]+\\.gif$" matches on "\\123.gif" but not on "\\test.gif"' + ), + block_domains=dict( + type='list', elements='str', required=False, default=[], + aliases=['block', 'block_list', 'blacklist'], + description='Blacklist destination domains. You may use a regular expression, ' + 'use a comma or press Enter for new item. Examples: "mydomain.com" ' + 'matches on "*.mydomain.com"; "^https?:\\/\\/([a-zA-Z]+)\\.mydomain\\." ' + 'matches on "http(s)://textONLY.mydomain.*"; "\\.gif$" matches on "*.gif" ' + 'but not on "\\*.gif\\test"; "\\[0-9]+\\.gif$" matches on "\\123.gif" but ' + 'not on "\\test.gif"' + ), + block_user_agents=dict( + type='list', elements='str', required=False, default=[], + aliases=['block_ua', 'block_list_ua'], + description='Block user-agents. You may use a regular expression, use a comma or ' + 'press Enter for new item. Examples: "^(.)+Macintosh(.)+Firefox/37\\.0" ' + 'matches on "Macintosh version of Firefox revision 37.0"; "^Mozilla" ' + 'matches on "all Mozilla based browsers"' + ), + block_mime_types=dict( + type='list', elements='str', required=False, default=[], + aliases=['block_mime', 'block_list_mime'], + description='Block specific MIME type reply. You may use a regular expression, ' + 'use a comma or press Enter for new item. Examples: "video/flv" matches ' + 'on "Flash Video"; "application/x-javascript" matches on "javascripts"' + ), + exclude_google=dict( + type='list', elements='str', required=False, default=[], + aliases=['safe_list_google'], + description='The domain that will be allowed to use Google GSuite. ' + 'All accounts that are not in this domain will be blocked to use it' + ), + youtube_filter=dict( + type='str', required=False, aliases=['youtube'], + choices=['strict', 'moderate'], description='Youtube filter level' + ), + ports_tcp=dict( + type='list', elements='str', required=False, aliases=['p_tcp'], + default=[ + '80:http', '21:ftp', '443:https', '70:gopher', '210:wais', '1025-65535:unregistered ports', + '280:http-mgmt', '488:gss-http', '591:filemaker', '777:multiling http' + ], + description='Allowed destination TCP ports, you may use ranges (ex. 222-226) and ' + 'add comments with colon (ex. 22:ssh)' + ), + ports_ssl=dict( + type='list', elements='str', required=False, + default=['443:https'], aliases=['p_ssl'], + description='Allowed destination SSL ports, you may use ranges (ex. 222-226) and ' + 'add comments with colon (ex. 22:ssh)' + ), + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_auth.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_auth.py new file mode 100644 index 0000000..24c0ed2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_auth.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_auth import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + method=dict( + type='str', required=False, aliases=['type', 'target'], + description='The authentication backend to use - as shown in the ' + "WEB-UI at 'System - Access - Servers'. Per example: " + "'Local Database'" + ), + group=dict( + type='str', required=False, aliases=['local_group'], + description='Restrict access to users in the selected (local)group. ' + "NOTE: please be aware that users (or vouchers) which aren't " + "administered locally will be denied when using this option" + ), + prompt=dict( + type='str', required=False, aliases=['realm'], + default='OPNsense proxy authentication', + description='The prompt will be displayed in the authentication request window' + ), + ttl_h=dict( + type='int', required=False, default=2, aliases=['ttl', 'ttl_hours', 'credential_ttl'], + description='This specifies for how long (in hours) the proxy server assumes ' + 'an externally validated username and password combination is valid ' + '(Time To Live). When the TTL expires, the user will be prompted for ' + 'credentials again' + ), + processes=dict( + type='int', required=False, default=5, aliases=['proc'], + description='The total number of authenticator processes to spawn' + ), + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_cache.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_cache.py new file mode 100644 index 0000000..ffa1b99 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_cache.py @@ -0,0 +1,121 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_cache import Cache + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + +BLANK_VALUES = { + 'memory_cache_mode': 'default', +} + + +def run_module(): + module_args = dict( + memory_mb=dict( + type='int', required=False, default=256, aliases=['memory', 'mem'], + description='The cache memory size to use or zero to disable completely' + ), + size_mb=dict( + type='int', required=False, default=100, aliases=['size'], + description='The storage size for the local cache' + ), + directory=dict( + type='str', required=False, default='/var/squid/cache', aliases=['dir'], + description='The location for the local cache' + ), + slot_size=dict( + type='int', required=False, default=16384, + description='Defines the size of a database record used to store cached responses. ' + 'Value should be a multiple of the OS I/O page size.' + ), + swap_timeout=dict( + type='int', required=False, default=0, + description='Prevents Squid from reading/writing to disk if the operation exceeds the ' + 'specified timelimit in milliseconds.' + ), + max_swap_rate=dict( + type='int', required=False, default=0, aliases=['swap_rate'], + description='Limits disk access by setting a maximum I/O rate in swaps per second.' + ), + size_mb_max=dict( + type='int', required=False, default=4, + aliases=['maximum_object_size', 'max_size'], + description='The maximum object size' + ), + memory_kb_max=dict( + type='int', required=False, default=512, + aliases=['maximum_object_size_in_memory', 'max_memory', 'max_mem'], + description='The maximum object size' + ), + memory_cache_mode=dict( + type='str', required=False, default='default', + aliases=['cache_mode', 'mode'], + choices=['always', 'disk', 'network', 'default'], + description='Controls which objects to keep in the memory cache (cache_mem) always: ' + 'Keep most recently fetched objects in memory (default) disk: Only disk ' + 'cache hits are kept in memory, which means an object must first be ' + 'cached on disk and then hit a second time before cached in memory. ' + 'network: Only objects fetched from network is kept in memory' + ), + cache_linux_packages=dict( + type='bool', required=False, default=False, + description='Enable or disable the caching of packages for linux distributions. ' + 'This makes sense if you have multiple servers in your network and do ' + 'not host your own package mirror. This will reduce internet traffic ' + 'usage but increase disk access' + ), + cache_windows_updates=dict( + type='bool', required=False, default=False, + description='Enable or disable the caching of Windows updates. This makes sense ' + "if you don't have a WSUS server. If you can setup a WSUS server, " + 'this solution should be preferred' + ), + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + for field, value in BLANK_VALUES.items(): + if module.params[field] == value: + module.params[field] = '' # BlankDesc + + module_wrapper(Cache(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_forward.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_forward.py new file mode 100644 index 0000000..223e1d5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_forward.py @@ -0,0 +1,127 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_forward import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + interfaces=dict( + type='list', elements='str', required=False, default=['lan'], aliases=['ints'], + description='Interface(s) the proxy will bind to' + ), + port=dict(type='int', required=False, default=3128, aliases=['p']), + port_ssl=dict(type='int', required=False, default=3129, aliases=['p_ssl']), + transparent=dict( + type='bool', required=False, default=False, aliases=['transparent_mode'], + description='Enable transparent proxy mode. You will need a firewall rule to ' + 'forward traffic from the firewall to the proxy server. You may ' + 'leave the proxy interfaces empty, but remember to set a valid ACL ' + 'in that case' + ), + ssl_inspection=dict( + type='bool', required=False, default=False, aliases=['ssl_inspect', 'ssl'], + description='Enable SSL inspection mode, which allows to log HTTPS connections ' + 'information, such as requested URL and/or make the proxy act as a ' + 'man in the middle between the internet and your clients. Be aware ' + 'of the security implications before enabling this option. If you ' + 'plan to use transparent HTTPS mode, you need nat rules to reflect ' + 'your traffic' + ), + ssl_inspection_sni_only=dict( + type='bool', required=False, default=False, aliases=['ssl_sni_only'], + description='Do not decode and/or filter SSL content, only log requested domains ' + 'and IP addresses. Some old servers may not provide SNI, so their ' + 'addresses will not be indicated' + ), + ssl_ca=dict( + type='str', required=False, aliases=['ca'], + description='Select a Certificate Authority to use' + ), + ssl_exclude=dict( + type='list', elements='str', required=False, default=[], + description='A list of sites which may not be inspected, for example bank sites. ' + 'Prefix the domain with a . to accept all subdomains (e.g. .google.com)' + ), + ssl_cache_mb=dict( + type='int', required=False, default=4, aliases=['ssl_cache', 'cache'], + description='The maximum size (in MB) to use for SSL certificates' + ), + ssl_workers=dict( + type='int', required=False, default=5, aliases=['workers'], + description='The number of ssl certificate workers to use (sslcrtd_children)' + ), + allow_interface_subnets=dict( + type='bool', required=False, default=True, aliases=['allow_subnets'], + description='When enabled the subnets of the selected interfaces will be ' + 'added to the allow access list' + ), + # snmp tab + snmp=dict( + type='bool', required=False, default=False, + description='Enable or disable the squid SNMP Agent' + ), + port_snmp=dict(type='int', required=False, default=3401, aliases=['p_snmp']), + snmp_password=dict( + type='str', required=False, default='public', no_log=True, + aliases=['snmp_community', 'snmp_pwd'], + description='The password for access to SNMP agent' + ), + # ftp tab + interfaces_ftp=dict( + type='list', elements='str', required=False, default=[], aliases=['ints_ftp'], + description='Interface(s) the ftp proxy will bind to' + ), + port_ftp=dict(type='int', required=False, default=2121, aliases=['p_ftp']), + transparent_ftp=dict( + type='bool', required=False, default=False, + description='Enable transparent ftp proxy mode to forward all requests ' + 'for destination port 21 to the proxy server without any ' + 'additional configuration' + ), + **RELOAD_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_general.py new file mode 100644 index 0000000..6efb0da --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_general.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + +BLANK_VALUES = { + 'errors': 'squid', + 'log_target': 'file', + 'handling_forwarded_for': 'default', +} + + +def run_module(): + module_args = dict( + errors=dict( + type='str', required=False, default='opnsense', aliases=['error_pages'], + choices=['opnsense', 'custom', 'squid'], + description='The proxy error pages can be altered, default layout uses ' + 'OPNsense content, when Squid is selected the content for the ' + 'selected language will be used (standard squid layout), Custom ' + 'offers the possibility to upload your own theme content' + ), + icp_port=dict(type='int', required=False, aliases=['icp']), + log=dict(type='bool', required=False, default=True), + log_store=dict(type='bool', required=False, default=True), + log_target=dict( + type='str', required=False, default='file', + choices=['file', 'file_extendend', 'file_json', 'syslog', 'syslog_json'], + description='Send log data to the selected target. When syslog is selected, ' + 'facility local 4 will be used to send messages of info level for these logs' + ), + log_ignore=dict( + type='list', elements='str', required=False, default=[], + description='Type subnets/addresses you want to ignore for the access.log' + ), + dns_servers=dict( + type='list', elements='str', required=False, default=[], + description='IPs of alternative DNS servers you like to use' + ), + use_via_header=dict( + type='bool', required=False, default=True, + description='If set (default), Squid will include a Via header in requests and replies ' + 'as required by RFC2616' + ), + handling_forwarded_for=dict( + type='str', required=False, default='default', + aliases=['forwarded_for_handling', 'forwarded_for', 'handle_ff'], + choices=['default', 'on', 'off', 'transparent', 'delete', 'truncate'], + description="Select what to do with X-Forwarded-For header. If set to: 'on', Squid will " + "append your client's IP address in the HTTP requests it forwards. By default " + "it looks like X-Forwarded-For: 192.1.2.3; If set to: 'off', it will appear as " + "X-Forwarded-For: unknown; 'transparent', Squid will not alter the X-Forwarded-For " + "header in any way; If set to: 'delete', Squid will delete the entire " + "X-Forwarded-For header; If set to: 'truncate', Squid will remove all existing " + "X-Forwarded-For entries, and place the client IP as the sole entry" + ), + hostname=dict( + type='str', required=False, aliases=['visible_hostname'], + description='The hostname to be displayed in proxy server error messages' + ), + email=dict( + type='str', required=False, default='admin@localhost.local', aliases=['visible_email'], + description='The email address displayed in error messages to the users' + ), + suppress_version=dict( + type='bool', required=False, default=False, + description='Suppress Squid version string info in HTTP headers and HTML error pages' + ), + connect_timeout=dict( + type='int', required=False, + description='This can help you when having connection issues with IPv6 enabled servers. ' + 'Set a value in seconds (1-120s)' + ), + handling_uri_whitespace=dict( + type='str', required=False, default='strip', + aliases=['uri_whitespace_handling', 'uri_whitespace', 'handle_uw'], + choices=['strip', 'deny', 'allow', 'encode', 'chop'], + description='Select what to do with URI that contain whitespaces. The current Squid ' + 'implementation of encode and chop violates RFC2616 by not using a 301 ' + 'redirect after altering the URL' + ), + pinger=dict( + type='bool', required=False, default=True, + description='Toggles the Squid pinger service. ' + 'This service is used in the selection of the best parent proxy' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + for field, value in BLANK_VALUES.items(): + if module.params[field] == value: + module.params[field] = '' # BlankDesc + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_icap.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_icap.py new file mode 100644 index 0000000..da02bc0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_icap.py @@ -0,0 +1,107 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_icap import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + request_url=dict( + type='str', required=False, default='icap://[::1]:1344/avscan', + aliases=['request', 'request_target'], + description='The url where the REQMOD requests should be sent to' + ), + response_url=dict( + type='str', required=False, default='icap://[::1]:1344/avscan', + aliases=['response', 'response_target'], + description='The url where the RESPMOD requests should be sent to' + ), + ttl=dict( + type='int', required=False, default=60, aliases=['default_ttl'] + ), + send_client_ip=dict( + type='bool', required=False, default=True, aliases=['send_client'], + description='If you enable this option, the client IP address will be sent to ' + 'the ICAP server. This can be useful if you want to filter traffic ' + 'based on IP addresses' + ), + send_username=dict( + type='bool', required=False, default=False, aliases=['send_user'], + description='If you enable this option, the username of the client will be sent ' + 'to the ICAP server. This can be useful if you want to filter traffic ' + 'based on usernames. Authentication is required to use usernames' + ), + encode_username=dict( + type='bool', required=False, default=False, + aliases=['user_encode', 'encode_user', 'enc_user'], + description='Use this option if your usernames need to be encoded' + ), + header_username=dict( + type='str', required=False, default='X-Username', + aliases=['header_user', 'user_header'], + description='The header which should be used to send the username to the ICAP server' + ), + preview=dict( + type='bool', required=False, default=True, + description='If you use previews, only a part of the data is sent ' + 'to the ICAP server. Setting this option can improve the performance' + ), + preview_size=dict( + type='int', required=False, default=1024, + description='Size of the preview which is sent to the ICAP server' + ), + exclude=dict( + type='list', elements='str', required=False, default=[], + description='Exclusion list destination domains.You may use a regular expression, ' + 'use a comma or press Enter for new item. Examples: "mydomain.com" matches ' + 'on "*.mydomain.com"; "https://([a-zA-Z]+)\\.mydomain\\." matches on ' + '"http(s)://textONLY.mydomain.*"; "\\.gif$" matches on "\\*.gif" but not on ' + '"\\*.gif\\test"; "\\[0-9]+\\.gif$" matches on "\\123.gif" but not on "\\test.gif"' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(General(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_match.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_match.py new file mode 100644 index 0000000..c50c312 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_match.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG, STATE_ONLY_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_pac_match import Match + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + +MONTH_MAPPING = { + 1: 'JAN', + 2: 'FEB', + 3: 'MAR', + 4: 'APR', + 5: 'MAY', + 6: 'JUN', + 7: 'JUL', + 8: 'AUG', + 9: 'SEP', + 10: 'OCT', + 11: 'NOV', + 12: 'DEC', +} + +WEEKDAY_MAPPING = { + 1: 'MON', + 2: 'TUE', + 3: 'WED', + 4: 'THU', + 5: 'FRI', + 6: 'SAT', + 7: 'SUN', +} + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, description='Unique name for the match', + ), + description=dict(type='str', required=False, aliases=['desc']), + negate=dict( + type='bool', required=False, default=False, + description='Negate this match. ' + 'For example you can match if a host is not inside a network' + ), + type=dict( + type='str', required=False, default='url_matches', + choices=[ + 'url_matches', 'hostname_matches', 'dns_domain_is', 'destination_in_net', + 'my_ip_in_net', 'plain_hostname', 'is_resolvable', 'dns_domain_levels', + 'weekday_range', 'date_range', 'time_range', + ], + description='The type of the match. Depending on the match, you will need ' + 'different arguments', + ), + hostname=dict( + type='str', required=False, + description='A hostname pattern like *.opnsense.org', + ), + url=dict( + type='str', required=False, + description='A URL pattern like forum.opnsense.org/index*', + ), + network=dict( + type='str', required=False, + description='The network address to match in CIDR notation for example ' + 'like 127.0.0.1/8 or ::1/128', + ), + domain_level_from=dict( + type='int', required=False, default=0, aliases=['domain_from'], + description='The minimum amount of dots in the domain name', + ), + domain_level_to=dict( + type='int', required=False, default=0, aliases=['domain_to'], + description='The maximum amount of dots in the domain name', + ), + hour_from=dict( + type='int', required=False, default=0, aliases=['time_from'], + description='Start hour for match-period', + ), + hour_to=dict( + type='int', required=False, default=0, aliases=['time_to'], + description='End hour for match-period', + ), + month_from=dict( + type='int', required=False, default=1, aliases=['date_from'], + choices=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + description='Start month for match-period', + ), + month_to=dict( + type='int', required=False, default=1, aliases=['date_to'], + choices=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + description='End month for match-period', + ), + weekday_from=dict( + type='int', required=False, default=1, aliases=['day_from'], + choices=[1, 2, 3, 4, 5, 6, 7], + description='Start weekday for match-period. 1 = monday, 7 = sunday', + ), + weekday_to=dict( + type='int', required=False, default=1, aliases=['day_to'], + choices=[1, 2, 3, 4, 5, 6, 7], + description='End weekday for match-period. 1 = monday, 7 = sunday', + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + for day_field in ['weekday_from', 'weekday_to']: + module.params[day_field] = WEEKDAY_MAPPING[module.params[day_field]] + + for month_field in ['month_from', 'month_to']: + module.params[month_field] = MONTH_MAPPING[module.params[month_field]] + + module_wrapper(Match(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_proxy.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_proxy.py new file mode 100644 index 0000000..014f8dd --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_proxy.py @@ -0,0 +1,71 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG, STATE_ONLY_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_pac_proxy import Proxy + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=True, description='Unique name for the proxy', + ), + description=dict(type='str', required=False, aliases=['desc']), + type=dict( + type='str', required=False, default='proxy', + choices=['proxy', 'direct', 'http', 'https', 'socks', 'socks4', 'socks5'], + description="Usually you should use 'direct' for a direct connection or " + "'proxy' for a Proxy", + ), + url=dict( + type='str', required=False, + description='A proxy URL in the form proxy.example.com:3128', + ), + **RELOAD_MOD_ARG, + **STATE_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module.params['type'] = module.params['type'].upper() + + module_wrapper(Proxy(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_rule.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_rule.py new file mode 100644 index 0000000..96662bf --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_pac_rule.py @@ -0,0 +1,85 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG, STATE_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_pac_rule import Rule + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + description=dict( + type='str', required=True, aliases=['desc', 'name'], + description='Unique description used to identify existing rules' + ), + matches=dict( + type='list', elements='str', required=False, default=[], + description='Matches you want to use in this rule. This matches ' + 'are joined using the selected separator', + ), + proxies=dict( + type='list', elements='str', required=False, default=[], + description='Proxies you want to use address using this rule', + ), + join_type=dict( + type='str', required=False, default='and', + aliases=['join'], choices=['and', 'or'], + description="A separator to join the matches. 'or' means any match " + 'can be true which can be used to configure the same ' + "proxy for multiple networks while 'and' means all matches " + 'must be true which can be used to assign the proxy in a ' + 'more detailed way', + ), + match_type=dict( + type='str', required=False, default='if', + aliases=['operator'], choices=['if', 'unless'], + description="Choose 'if' in case any case you want to ensure a match to " + "evaluate as is, else choose 'unless' if you want the negated " + 'version. Unless is used if you want to use the proxy for every ' + 'host but not for some special ones', + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Rule(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_parent.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_parent.py new file mode 100644 index 0000000..2574716 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_parent.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_parent import Parent + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + host=dict( + type='str', required=False, aliases=['ip'], + description='Parent proxy IP address or hostname' + ), + auth=dict( + type='bool', required=False, default=False, + description='Enable authentication against the parent proxy' + ), + user=dict( + type='str', required=False, default='placeholder', + description='Set a username if parent proxy requires authentication' + ), + password=dict( + type='str', required=False, default='placeholder', no_log=True, + description='Set a username if parent proxy requires authentication' + ), + port=dict(type='int', required=False, aliases=['p']), + local_domains=dict( + type='list', elements='str', required=False, default=[], aliases=['domains'], + description='Domains not to be sent via parent proxy' + ), + local_ips=dict( + type='list', elements='str', required=False, default=[], aliases=['ips'], + description='IP addresses not to be sent via parent proxy' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Parent(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_remote_acl.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_remote_acl.py new file mode 100644 index 0000000..bb06718 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_remote_acl.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, RELOAD_MOD_ARG, STATE_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_remote_acl import Acl + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + file=dict( + type='str', required=True, aliases=['filename'], + description='Unique file-name to store the remote acl in' + ), + url=dict( + type='str', required=False, + description='Url to fetch the acl from' + ), + username=dict( + type='str', required=False, aliases=['user'], + description='Optional user for authentication' + ), + password=dict( + type='str', required=False, aliases=['pwd'], + description='Optional password for authentication', + no_log=True, + ), + categories=dict( + type='list', elements='str', required=False, default=[], + aliases=['cat', 'filter'], + description='Select categories to use, leave empty for all. ' + 'Categories are visible in the WEB-UI after initial download' + ), + verify_ssl=dict( + type='bool', required=False, default=True, aliases=['verify'], + description='If certificate validation should be done - relevant if ' + 'self-signed certificates are used on the target server!' + ), + description=dict( + type='str', required=False, aliases=['desc'], + description='A description to explain what this blacklist is intended for' + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Acl(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_traffic.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_traffic.py new file mode 100644 index 0000000..9b4385b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/webproxy_traffic.py @@ -0,0 +1,75 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.webproxy_traffic import Traffic + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/webproxy.html' + + +def run_module(): + module_args = dict( + download_kb_max=dict( + type='str', required=False, default='2048', + aliases=['download_max', 'download', 'dl_max', 'dl'], + description='The maximum size for downloads in kilobytes (leave empty to disable)' + ), + upload_kb_max=dict( + type='str', required=False, default='1024', + aliases=['upload_max', 'upload', 'ul_max', 'ul'], + description='The maximum size for uploads in kilobytes (leave empty to disable)' + ), + throttle_kb_bandwidth=dict( + type='str', required=False, default='1024', + aliases=['throttle_bandwidth', 'throttle_bw', 'bandwidth', 'bw'], + description='The allowed overall bandwidth in kilobits per second (leave empty to disable)' + ), + throttle_kb_host_bandwidth=dict( + type='str', required=False, default='256', + aliases=['throttle_host_bandwidth', 'throttle_host_bw', 'host_bandwidth', 'host_bw'], + description='The allowed per host bandwidth in kilobits per second (leave empty to disable)' + ), + **RELOAD_MOD_ARG, + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Traffic(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_general.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_general.py new file mode 100644 index 0000000..c992e10 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_general.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/wireguard.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import Session + from ansible_collections.oxlorg.opnsense.plugins.module_utils.helper.main import \ + is_true, to_digit + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/wireguard.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/wireguard.html' + + +def run_module(): + module_args = dict( + **EN_ONLY_MOD_ARG, + **OPN_MOD_ARGS, + **RELOAD_MOD_ARG, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {'enabled': module.params['enabled']}, + } + ) + + with Session(module=module) as s: + is_enabled = is_true( + s.get(cnf={ + 'module': 'wireguard', + 'controller': 'general', + 'command': 'get', + })['general']['enabled'] + ) + result['diff']['before']['enabled'] = is_enabled + + if is_enabled != module.params['enabled']: + result['changed'] = True + + if not module.check_mode: + s.post(cnf={ + 'module': 'wireguard', + 'controller': 'general', + 'command': 'set', + 'data': {'general': {'enabled': to_digit(module.params['enabled'])}} + }) + s.post(cnf={ + 'module': 'wireguard', + 'controller': 'service', + 'command': 'reconfigure', + }) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_peer.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_peer.py new file mode 100644 index 0000000..7969aec --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_peer.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/wireguard.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wireguard_peer import Peer + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/wireguard.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/wireguard.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + public_key=dict(type='str', required=False, aliases=['pubkey', 'pub']), + psk=dict(type='str', required=False, no_log=True), + allowed_ips=dict( + type='list', elements='str', required=False, default=[], + aliases=[ + 'tunnel_ips', 'tunnel_ip', 'tunneladdress', 'tunnel_adresses', + 'addresses', 'address', 'tunnel_address', 'allowed', + ] + ), + server=dict( + type='str', required=False, + aliases=['target', 'server_address', 'serveraddress', 'endpoint'] + ), + port=dict(type='int', required=False), + keepalive=dict(type='int', required=False), + servers=dict(type='list', elements='str', required=False, default=[], aliases=['instances']), + link_servers=dict( + type='bool', default=False, required=False, + description="Whether you want to link servers instance by the peer. " + "If that is the case - you should disable 'link_peers' on your server-entries. " + "Will always be true if you supply any servers" + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Peer(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_server.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_server.py new file mode 100644 index 0000000..afe88db --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_server.py @@ -0,0 +1,87 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/wireguard.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.wrapper import module_wrapper + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.oxlorg.opnsense.plugins.module_utils.main.wireguard_server import Server + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/wireguard.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/wireguard.html' + + +def run_module(): + module_args = dict( + name=dict(type='str', required=True), + public_key=dict(type='str', required=False, aliases=['pubkey', 'pub']), + private_key=dict(type='str', required=False, no_log=True, aliases=['privkey', 'priv']), + port=dict(type='int', required=False), + mtu=dict(type='int', required=False, default=1420), + dns_servers=dict( + type='list', elements='str', required=False, default=[], aliases=['dns'], + ), + allowed_ips=dict( + type='list', elements='str', required=False, default=[], + aliases=[ + 'tunnel_ips', 'tunnel_ip', 'tunneladdress', 'tunnel_adresses', + 'addresses', 'address', 'tunnel_address', 'allowed', + ] + ), + disable_routes=dict(type='bool', default=False, required=False, aliases=['disableroutes']), + gateway=dict(type='str', required=False, aliases=['gw']), + vip=dict( + type='str', required=False, + aliases=['vip_depend', 'carp', 'carp_depend'], + description='The Virtual-CARP-IP (CARP VHID) to depend on. ' + 'When this virtual address is not in master state, then the instance will be shutdown' + ), + peers=dict(type='list', elements='str', required=False, default=[], aliases=['clients']), + link_peers=dict( + type='bool', default=True, required=False, + description="Whether you want to link peers by the server instance. " + "If that is the case - you should disable 'link_servers' on your peer-entries. " + "Will always be true if you supply any peers." + ), + **RELOAD_MOD_ARG, + **STATE_MOD_ARG, + **OPN_MOD_ARGS, + ) + + result = dict( + changed=False, + diff={ + 'before': {}, + 'after': {}, + } + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + module_wrapper(Server(module=module, result=result)) + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_show.py b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_show.py new file mode 100644 index 0000000..2ca6de5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/plugins/modules/wireguard_show.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2025, Pascal Rath +# GNU General Public License v3.0+ (see https://www.gnu.org/licenses/gpl-3.0.txt) + +# see: https://docs.opnsense.org/development/api/plugins/wireguard.html + +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.oxlorg.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS + from ansible_collections.oxlorg.opnsense.plugins.module_utils.base.api import single_get + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://ansible-opnsense.oxl.app/modules/wireguard.html' +# EXAMPLES = 'https://ansible-opnsense.oxl.app/modules/wireguard.html' + + +def run_module(): + module_args = dict( + **OPN_MOD_ARGS, + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + info = single_get( + module=module, + cnf={ + 'module': 'wireguard', + 'controller': 'service', + 'command': 'show', + } + ) + + if 'response' in info: + info = info['response'] + + if isinstance(info, str): + info = info.strip() + + module.exit_json(data=info) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/requirements.txt b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/requirements.txt new file mode 100644 index 0000000..c396dbb --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/requirements.txt @@ -0,0 +1,2 @@ +# pip requirements +httpx diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_cleanup.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_cleanup.yml new file mode 100644 index 0000000..9331d70 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_cleanup.yml @@ -0,0 +1,1154 @@ +--- + +- name: Cleaning up firewall config + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Cleanup Rules + oxlorg.opnsense.rule_multi: + multi_control: + purge_all: true + match_fields: ['description'] + + - name: Cleanup Aliases (skips referenced by other) + oxlorg.opnsense.alias_multi: + multi_control: + purge_all: true + fail_process: false + + - name: Cleanup Aliases (previously referenced) + oxlorg.opnsense.alias_multi: + multi_control: + purge_all: true + fail_process: false + + - name: Cleanup packages + oxlorg.opnsense.package: + name: ['os-hw-probe', 'os-dmidecode'] + action: "{{ item }}" + timeout: 60 + diff: false + loop: + - 'unlock' + - 'remove' + + - name: Cleanup cron jobs + oxlorg.opnsense.cron: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + + - name: Cleanup routes + oxlorg.opnsense.route: + description: "{{ item }}" + network: '192.168.0.0/32' + gateway: 'LAN_GW' + state: 'absent' + match_fields: ['description'] + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + + - name: Cleanup Unbound General + oxlorg.opnsense.unbound_general: + enabled: false + reload: false # speed + + - name: Cleanup Unbound DNS ACLs + oxlorg.opnsense.unbound_acl: + name: "{{ item }}" + state: 'absent' + reload: false # speed + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup Unbound DNS-over-TLS + oxlorg.opnsense.unbound_dot: + target: "{{ item }}" + state: 'absent' + reload: false # speed + loop: + - '1.1.1.1' + - '1.1.1.2' + - '1.1.1.3' + - '1.1.1.4' + + - name: Cleanup Unbound DNS-Forwarding's + oxlorg.opnsense.unbound_forward: + domain: "{{ item.d }}" + target: "{{ item.t }}" + state: 'absent' + reload: false # speed + loop: + - {d: 'fwd.opnsense.test.oxlorg.net', t: '1.1.1.1'} + - {d: 'fwd.opnsense.test.oxlorg.net', t: '1.1.1.2'} + - {d: '', t: '1.1.1.3'} + - {d: '', t: '1.1.1.4'} + + - name: Cleanup Unbound DNS host-override aliases + oxlorg.opnsense.unbound_host_alias: + description: "{{ item }}" + alias: 'dummy' + domain: 'dummy' + state: 'absent' + match_fields: ['description'] + reload: false # speed + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup Unbound DNS host-overrides + oxlorg.opnsense.unbound_host: + description: "{{ item }}" + hostname: 'dummy' + domain: 'dummy' + state: 'absent' + match_fields: ['description'] + reload: false # speed + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup + oxlorg.opnsense.unbound_dnsbl: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + + - name: Cleanup syslog + oxlorg.opnsense.syslog: + description: "{{ item }}" + target: '192.168.0.1' + state: 'absent' + match_fields: ['description'] + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + - 'ANSIBLE_TEST_3' + + - name: Cleanup ipsec certs + oxlorg.opnsense.ipsec_cert: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_3_1' + + - name: Cleanup shaper pipes + oxlorg.opnsense.shaper_pipe: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_3_1' + + - name: Cleanup shaper queues + oxlorg.opnsense.shaper_queue: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup shaper rules + oxlorg.opnsense.shaper_rule: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup monit services + oxlorg.opnsense.monit_service: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + + - name: Cleanup monit alerts + oxlorg.opnsense.monit_alert: + recipient: "{{ item }}" + state: 'absent' + loop: + - 'alert@monit.opnsense.test.oxlorg.net' + - 'alert2@monit.opnsense.test.oxlorg.net' + + - name: Cleanup monit tests + oxlorg.opnsense.monit_test: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup wireguard servers + oxlorg.opnsense.wireguard_server: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup wireguard peers + oxlorg.opnsense.wireguard_peer: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_3_1' + + - name: Cleanup wireguard general + oxlorg.opnsense.wireguard_general: + enabled: false + + - name: Cleanup VIPs + oxlorg.opnsense.interface_vip: + interface: "{{ item.int }}" + address: "{{ item.ip }}" + state: 'absent' + loop: + - {int: 'lan', ip: '192.168.1.1/30'} + - {int: 'opt1', ip: '192.168.2.1/24'} + - {int: 'opt1', ip: '2001:db8::1/128'} + + - name: Cleanup vlan interfaces + oxlorg.opnsense.interface_vlan: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup vxlan interfaces + oxlorg.opnsense.interface_vxlan: + id: "{{ item }}" + state: 'absent' + loop: + - 100 + - 101 + + - name: Cleanup lagg interfaces + oxlorg.opnsense.interface_lagg: + device: 'lagg0' + state: 'absent' + + - name: Cleanup GRE interfaces + oxlorg.opnsense.interface_gre: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup Bridge interfaces + oxlorg.opnsense.interface_bridge: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup GIF interfaces + oxlorg.opnsense.interface_gif: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup source-nat + oxlorg.opnsense.source_nat: + description: "{{ item }}" + state: 'absent' + match_fields: ['description'] + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup on-to-one nat + oxlorg.opnsense.one_to_one: + description: "{{ item }}" + state: 'absent' + match_fields: ['description'] + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup FRR general settings + oxlorg.opnsense.frr_general: + enabled: false + profile: 'traditional' + carp: false + snmp_agentx: false + log: true + log_level: 'notifications' + + - name: Cleanup FRR BFD general settings + oxlorg.opnsense.frr_bfd_general: + enabled: false + + - name: Cleanup FRR BFD neighbors + oxlorg.opnsense.frr_bfd_neighbor: + ip: "{{ item }}" + state: 'absent' + loop: + - '10.0.0.1' + - '10.0.0.0/28' + + - name: Cleanup FRR BGP general settings + oxlorg.opnsense.frr_bgp_general: + as_number: 1337 + enabled: false + + - name: Cleanup FRR BGP neighbors + oxlorg.opnsense.frr_bgp_neighbor: + description: "{{ item }}" + state: 'absent' + match_fields: ['description'] + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + + - name: Cleanup FRR BGP route-maps + oxlorg.opnsense.frr_bgp_route_map: + name: "{{ item.name }}" + id: "{{ item.id }}" + state: 'absent' + loop: + - {'name': 'ANSIBLE_TEST_1_1', id: 45} + - {'name': 'ANSIBLE_TEST_1_1', id: 51} + - {'name': 'ANSIBLE_TEST_1_2', id: 65} + - {'name': 'ANSIBLE_TEST_2_1', id: 65} + - {'name': 'ANSIBLE_TEST_2_2', id: 66} + - {'name': 'ANSIBLE_TEST_2_3', id: 67} + - {'name': 'ANSIBLE_TEST_3_1', id: 50} + + - name: Cleanup FRR BGP prefix lists + oxlorg.opnsense.frr_bgp_prefix_list: + name: "{{ item.n }}" + seq: "{{ item.s | default(omit) }}" + state: 'absent' + loop: + - {'n': 'ANSIBLE_TEST_1_1', 's': 55} + - {'n': 'ANSIBLE_TEST_1_2', 's': 56} + - {'n': 'ANSIBLE_TEST_1_2', 's': 57} + - {'n': 'ANSIBLE_TEST_2_1', 's': 50} + - {'n': 'ANSIBLE_TEST_2_1', 's': 51} + - {'n': 'ANSIBLE_TEST_3_1', 's': 50} + - {'n': 'ANSIBLE_TEST_3_1', 's': 51} + + - name: Cleanup FRR BGP community lists + oxlorg.opnsense.frr_bgp_community_list: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup FRR BGP as paths + oxlorg.opnsense.frr_bgp_as_path: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup FRR BGP redistributions + oxlorg.opnsense.frr_bgp_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup FRR BGP Peer Groups + oxlorg.opnsense.frr_bgp_peer_group: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup OSPF general settings + oxlorg.opnsense.frr_ospf_general: + enabled: false + + - name: Cleanup OSPFv3 general settings + oxlorg.opnsense.frr_ospf3_general: + enabled: false + + - name: Cleanup OSPFv3 interfaces + oxlorg.opnsense.frr_ospf3_interface: + interface: "{{ item }}" + state: 'absent' + match_fields: ['interface'] + loop: + - 'opt1' + - 'lan' + + - name: Cleanup OSPF route-maps + oxlorg.opnsense.frr_ospf_route_map: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup OSPFv3 route-maps + oxlorg.opnsense.frr_ospf3_route_map: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup OSPF networks + oxlorg.opnsense.frr_ospf_network: + ip: "{{ item.ip }}" + mask: "{{ item.mask }}" + state: 'absent' + match_fields: ['ip', 'mask'] + loop: + - {'ip': '10.0.1.0', 'mask': 30} + - {'ip': '10.0.2.0', 'mask': 24} + - {'ip': '10.0.3.0', 'mask': 24} + + - name: Cleanup OSPFv3 networks + oxlorg.opnsense.frr_ospf3_network: + ip: "{{ item.ip }}" + mask: "{{ item.mask }}" + state: 'absent' + match_fields: ['ip', 'mask'] + loop: + - {'ip': '2001:db8:1::', 'mask': 64} + - {'ip': '2001:db8::', 'mask': 64} + + - name: Cleanup OSPF prefix-lists + oxlorg.opnsense.frr_ospf_prefix_list: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_3_1' + + - name: Cleanup OSPFv3 prefix-lists + oxlorg.opnsense.frr_ospf3_prefix_list: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_3_1' + + - name: Cleanup FRR OSPF redistributions + oxlorg.opnsense.frr_ospf_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup FRR OSPFv4 redistributions + oxlorg.opnsense.frr_ospf3_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup BIND general settings + oxlorg.opnsense.bind_general: + enabled: false + + - name: Cleanup BIND blocklist settings + oxlorg.opnsense.bind_blocklist: + enabled: false + + - name: Cleanup BIND Records + oxlorg.opnsense.bind_record_multi: + multi_control: + purge_all: true + register: bind_cleanup1 + failed_when: + - bind_cleanup1.failed + - "'does not seem to exist' not in bind_cleanup1.msg" + + - name: Cleanup BIND Domains + oxlorg.opnsense.bind_domain: + name: "{{ item }}" + state: 'absent' + loop: + - 'test1.oxlorg' + - 'test2.oxlorg' + - 'test3.oxlorg' + - 'test4.oxlorg' + + - name: Cleanup BIND ACLs + oxlorg.opnsense.bind_acl: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + - 'ANSIBLE_TEST_3_1' + - 'ANSIBLE_TEST_3_2' + when: not ansible_check_mode + + - name: Cleanup WebProxy General + oxlorg.opnsense.webproxy_general: + enabled: false + + - name: Cleanup WebProxy Cache + oxlorg.opnsense.webproxy_cache: + + - name: Cleanup WebProxy Parent + oxlorg.opnsense.webproxy_parent: + enabled: false + + - name: Cleanup WebProxy Traffic + oxlorg.opnsense.webproxy_traffic: + enabled: false + + - name: Cleanup WebProxy Forward + oxlorg.opnsense.webproxy_forward: + + - name: Cleanup WebProxy ACL + oxlorg.opnsense.webproxy_acl: + + - name: Cleanup WebProxy ICAP + oxlorg.opnsense.webproxy_icap: + enabled: false + + - name: Cleanup WebProxy Auth + oxlorg.opnsense.webproxy_auth: + + - name: Cleanup WebProxy Remote-ACLs + oxlorg.opnsense.webproxy_remote_acl: + file: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLETEST1' + - 'ANSIBLETEST2' + + - name: Cleanup WebProxy PAC-Rule + oxlorg.opnsense.webproxy_pac_rule: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup WebProxy PAC-Proxy + oxlorg.opnsense.webproxy_pac_proxy: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup WebProxy PAC-Match + oxlorg.opnsense.webproxy_pac_match: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup IPSec PSK + oxlorg.opnsense.ipsec_psk: + identity: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE@TEST1' + - 'ANSIBLE@TEST2' + - 'ANSIBLE@TEST2X' + + - name: Cleanup IPSec childs + oxlorg.opnsense.ipsec_child: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup IPSec connections + oxlorg.opnsense.ipsec_connection: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_3_1' + - 'ANSIBLE_TEST_4_1' + - 'ANSIBLE_TEST_5_1' + + - name: Cleanup IPSec pools + oxlorg.opnsense.ipsec_pool: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + + - name: Cleanup IPSec VTIs + oxlorg.opnsense.ipsec_vti: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup IPSec Local-Auth + oxlorg.opnsense.ipsec_auth_local: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup IPSec Remote-Auth + oxlorg.opnsense.ipsec_auth_remote: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup IDS General + oxlorg.opnsense.ids_general: + interfaces: ['opt1'] + enabled: false + + - name: Cleanup IDS Ruleset + oxlorg.opnsense.ids_ruleset: + name: 'ET open/compromised' + enabled: false + + - name: Cleanup IDS Rule + oxlorg.opnsense.ids_rule: + sid: 2400000 + + - name: Cleanup IDS User-Rule + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + + - name: Cleanup IDS Policy + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + state: absent + + - name: Cleanup IDS Policy-Rule + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + state: absent + + - name: Cleanup OpenVPN Client + oxlorg.opnsense.openvpn_client: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + + - name: Cleanup OpenVPN Client-Overwrite + oxlorg.opnsense.openvpn_client_override: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup OpenVPN Server + oxlorg.opnsense.openvpn_server: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_3_1' + + - name: Cleanup OpenVPN Static-Keys + oxlorg.opnsense.openvpn_static_key: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_3_1' + + - name: Cleanup Nginx General + oxlorg.opnsense.nginx_general: + enabled: false + + - name: Cleanup Nginx Upstream-Server + oxlorg.opnsense.nginx_upstream_server: + name: 'ANSIBLE@TEST1' + state: 'absent' + + - name: Cleanup Gateways + oxlorg.opnsense.gateway: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + + - name: Cleanup Interface groups + oxlorg.opnsense.rule_interface_group: + name: 'ANSIBLE_TEST' + state: 'absent' + + - name: Cleanup dhcrelay + oxlorg.opnsense.dhcrelay_relay: + interface: '{{ if_dhcrelay }}' + state: 'absent' + vars: + if_dhcrelay: "{{ lookup('ansible.builtin.env', 'TEST_DHCRELAY_IF') | default('lan', true) }}" + + - name: Cleanup dhcrelay-destination + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + + - name: Cleanup DHCP-reservations + oxlorg.opnsense.dhcp_reservation: + ip: "{{ item }}" + state: 'absent' + loop: + - '192.168.69.76' + - '192.168.69.86' + + - name: Cleanup DHCP-Controlagent + oxlorg.opnsense.dhcp_controlagent: + enabled: false + http_host: '127.0.0.1' + http_port: 8000 + + - name: Cleanup DHCP Settings + oxlorg.opnsense.dhcp_general: + enabled: false + + - name: Cleanup DHCP Subnets + oxlorg.opnsense.dhcp_subnet: + subnet: "{{ item }}" + state: 'absent' + loop: + - '192.168.69.0/24' + - '192.168.88.0/24' + - '192.168.89.0/24' + + - name: Cleanup ACME Certificates + oxlorg.opnsense.acme_certificate: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + + - name: Cleanup ACME General + oxlorg.opnsense.acme_general: + enabled: false + + - name: Cleanup ACME Account + oxlorg.opnsense.acme_account: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_DUMMY_1_1' + + - name: Cleanup ACME Action + oxlorg.opnsense.acme_action: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + - 'ANSIBLE_TEST_1_5' + - 'ANSIBLE_TEST_1_6' + - 'ANSIBLE_TEST_1_7' + - 'ANSIBLE_TEST_1_8' + - 'ANSIBLE_TEST_1_9' + - 'ANSIBLE_TEST_1_10' + - 'ANSIBLE_TEST_DUMMY_1_1' + + - name: Cleanup ACME Validation + oxlorg.opnsense.acme_validation: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + - 'ANSIBLE_TEST_1_5' + - 'ANSIBLE_TEST_1_6' + - 'ANSIBLE_TEST_1_7' + - 'ANSIBLE_TEST_1_8' + - 'ANSIBLE_TEST_1_9' + - 'ANSIBLE_TEST_1_10' + - 'ANSIBLE_TEST_DUMMY_1_1' + + - name: Cleanup Postfix General + oxlorg.opnsense.postfix_general: + enabled: false + + - name: Cleanup Postfix Address + oxlorg.opnsense.postfix_address: + address: "{{ item }}" + state: 'absent' + loop: + - 'alice@example.com' + - 'bob@example.com' + + - name: Cleanup Postfix Domain + oxlorg.opnsense.postfix_domain: + domainname: "{{ item }}" + state: 'absent' + loop: + - 'example.com' + - 'example.net' + + - name: Cleanup Postfix Headercheck + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: '{{ item }}' + state: 'absent' + loop: + - 'WHILE_RECEIVING' + - 'WHILE_DELIVERING' + + - name: Cleanup Postfix Recipient + oxlorg.opnsense.postfix_recipient: + address: '{{ item }}' + state: 'absent' + loop: + - 'alice@example.com' + - 'bob@example.com' + + - name: Cleanup Postfix Recipient BCC + oxlorg.opnsense.postfix_recipientbcc: + address: '{{ item }}' + state: 'absent' + loop: + - 'alice@example.com' + - 'bob@example.com' + + - name: Cleanup Postfix Sender + oxlorg.opnsense.postfix_sender: + address: '{{ item }}' + state: 'absent' + loop: + - 'alice@example.com' + - 'bob@example.com' + + - name: Cleanup Postfix Sender BCC + oxlorg.opnsense.postfix_senderbcc: + address: '{{ item }}' + state: 'absent' + loop: + - 'alice@example.com' + - 'bob@example.com' + + - name: Cleanup Postfix Sender Canonical + oxlorg.opnsense.postfix_sendercanonical: + address: '{{ item }}' + state: 'absent' + loop: + - 'alice@example.com' + - 'bob@example.com' + + - name: Cleanup IPSec Manual SPD + oxlorg.opnsense.ipsec_manual_spd: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup IPSec General + oxlorg.opnsense.ipsec_general: + + - name: Cleanup HASync + oxlorg.opnsense.hasync_general: + + - name: Cleanup Snapshots + oxlorg.opnsense.snapshot: + name: 'default' + activate: true + register: cl_snap1 + failed_when: + - cl_snap1.failed + - "'Unsupported' not in cl_snap1.msg | default('')" + + - name: Cleanup Snapshots + oxlorg.opnsense.snapshot: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + register: cl_snap2 + failed_when: + - cl_snap2.failed + - "'Unsupported' not in cl_snap2.msg | default('')" + + - name: Cleanup Wazuh Agent - disable + oxlorg.opnsense.wazuh_agent: + enabled: false + server_address: '127.0.0.1' # placeholder + register: cl_wazuh + failed_when: cl_wazuh.failed + + - name: Cleanup Users + oxlorg.opnsense.user: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + + - name: Cleanup Groups + oxlorg.opnsense.group: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + + - name: Cleanup dnsmasq General + oxlorg.opnsense.dnsmasq_general: + enabled: false + + - name: Cleanup dnsmasq Domain + oxlorg.opnsense.dnsmasq_domain: + domain: '{{ item }}' + state: 'absent' + loop: + - 'test1.oxlorg' + - 'test2.oxlorg' + + - name: Cleanup dnsmasq Host + oxlorg.opnsense.dnsmasq_host: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup dnsmasq Range + oxlorg.opnsense.dnsmasq_range: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup dnsmasq Option + oxlorg.opnsense.dnsmasq_option: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup dnsmasq Boot + oxlorg.opnsense.dnsmasq_boot: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup dnsmasq Tags + oxlorg.opnsense.dnsmasq_tag: + name: '{{ item }}' + state: 'absent' + loop: + - 'AnsibleTest1' + + - name: Cleanup HAProxy general settings + oxlorg.opnsense.haproxy_general_settings: + enabled: false + + - name: Cleanup HAProxy general cache settings + oxlorg.opnsense.haproxy_general_cache: + enabled: false + + - name: Cleanup HAProxy general defaults settings + oxlorg.opnsense.haproxy_general_defaults: + retries: 3 + redispatch: 'x-1' + + - name: Cleanup HAProxy general logging settings + oxlorg.opnsense.haproxy_general_logging: + host: '127.0.0.1' + facility: 'local0' + level: 'info' + + - name: Cleanup HAProxy general peers settings + oxlorg.opnsense.haproxy_general_peers: + enabled: false + + - name: Cleanup HAProxy general stats settings + oxlorg.opnsense.haproxy_general_stats: + enabled: false + + - name: Cleanup HAProxy general tuning settings + oxlorg.opnsense.haproxy_general_tuning: + root: false + + - name: Cleanup HAProxy maintenance settings + oxlorg.opnsense.haproxy_maintenance: + sync_certs: false + reload_service: false + restart_service: false + + - name: Cleanup HAProxy CPU rules + oxlorg.opnsense.haproxy_cpu: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + + - name: Cleanup HAProxy users + oxlorg.opnsense.haproxy_user: + name: "{{ item }}" + state: 'absent' + loop: + - 'testuser1' + - 'testuser2' + - 'ansible_test_user' + - 'groupuser1' + - 'groupuser2' + - 'groupuser3' + - 'stats_admin' + - 'stats_monitor' + + - name: Cleanup HAProxy groups + oxlorg.opnsense.haproxy_group: + name: "{{ item }}" + state: 'absent' + loop: + - 'testgroup1' + - 'testgroup2' + - 'emptygroup' + - 'ansible_test_group' + - 'stats_admins' + - 'stats_monitor' + + - name: Cleanup HAProxy ACLs + oxlorg.opnsense.haproxy_acl: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + - 'ANSIBLE_TEST_ACL_1' + + - name: Cleanup HAProxy Actions + oxlorg.opnsense.haproxy_action: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + - 'ANSIBLE_TEST_FCGI_ACTION_1' + - 'ANSIBLE_TEST_FCGI_ACTION_2' + + - name: Cleanup HAProxy Lua scripts + oxlorg.opnsense.haproxy_lua: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + + - name: Cleanup HAProxy FCGI applications + oxlorg.opnsense.haproxy_fcgi: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + + - name: Cleanup HAProxy Error files + oxlorg.opnsense.haproxy_errorfile: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_dependencies.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_dependencies.yml new file mode 100644 index 0000000..6de45ff --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_dependencies.yml @@ -0,0 +1,35 @@ +--- + +- name: Installing Test-Dependencies + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.package: + timeout: 120 + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Installing packages + oxlorg.opnsense.package: + name: "{{ item }}" + action: 'install' + register: opn_dep_install + failed_when: + - opn_dep_install.failed + - "'system needs to be upgraded beforehand' not in opn_dep_install.msg" + loop: + - 'os-squid' + - 'os-frr' + - 'os-bind' + - 'os-acme-client' + - 'os-postfix' + - 'os-nginx' + - 'os-haproxy' + - 'os-wazuh-agent' diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_multi.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_multi.yml new file mode 100644 index 0000000..ff1d559 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_multi.yml @@ -0,0 +1,582 @@ +--- + +# these tests should cover the main functionality of the abstracted mass management +# module-specific logic should be tested by dedicated test-cases (_multi.yml) + +# low-level checks should be done via unit-tests: +# module_utils/base/multi_base_1_mod_args.py => testing the module-argument generator +# module_utils/base/multi_base_2_parts_pytest.py => testing multi-handling methods +# module_utils/base/multi_base_3_full_pytest.py => 'end-to-end'-like tests + +- name: Testing Mass Management + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'alias' + + oxlorg.opnsense.alias_multi: + multi_control: + fail_verify: true + fail_process: true + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Purging all + oxlorg.opnsense.alias_multi: + multi_control: + purge_all: true + + - name: Fail required args (1) + oxlorg.opnsense.alias_multi: + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Fail required args (2) + oxlorg.opnsense.alias_multi: + multi_control: + purge_action: 'disable' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Fail required args (3) + oxlorg.opnsense.alias_multi: + multi_control: + purge_unconfigured: true + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Removing - do not exist + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + multi_control: + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.1.1' + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.1.1', '192.168.1.2'] + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/24' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.1.0/24', '192.168.2.0/24'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 80 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [80, 443] + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.2.0/24', '192.168.20.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [80, 8443] + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.2.0/24', '192.168.20.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [80, 8443] + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Changing few + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.12.0/24', '192.168.21.0/23'] + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [8080, 8443] + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.12.0/24', '192.168.21.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [8080, 8443] + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Disabling some + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + enabled: false + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + enabled: false + + multi_control: + enabled: false + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Checking count + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 6 + when: not ansible_check_mode + + - name: Checking disabled-state + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + enabled: false + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + enabled: false + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.12.0/24', '192.168.21.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [8080, 8443] + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Removing some + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + - name: 'ANSIBLE_TEST_0_2' + multi_control: + state: 'absent' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Checking count + oxlorg.opnsense.list: + register: opn11 + failed_when: > + 'data' not in opn11 or + opn11.data | length != 4 + when: not ansible_check_mode + + - name: Recreating + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + enabled: false + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + enabled: false + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.12.0/24', '192.168.21.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [8080, 8443] + when: not ansible_check_mode + + - name: Removing unconfigured/orphaned + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + enabled: false + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + enabled: false + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + multi_control: + purge_unconfigured: true + register: purge1 + failed_when: > + purge1.failed or + not purge1.changed + when: not ansible_check_mode + + - name: Checking count + oxlorg.opnsense.list: + register: opn15 + failed_when: > + 'data' not in opn15 or + opn15.data | length != 3 + when: not ansible_check_mode + + - name: Recreating + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + enabled: false + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + enabled: false + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.12.0/24', '192.168.21.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [8080, 8443] + when: not ansible_check_mode + + - name: Simple purge + oxlorg.opnsense.alias_multi: + multi_purge: + - name: 'ANSIBLE_TEST_0_1' + register: purge2 + failed_when: > + purge2.failed or + not purge2.changed + when: not ansible_check_mode + + - name: Listing aliases + oxlorg.opnsense.list: + register: opn16 + failed_when: > + 'data' not in opn16 or + opn16.data | length != 5 + when: not ansible_check_mode + + - name: Simple disable-purge + oxlorg.opnsense.alias_multi: + multi_purge: + - name: 'ANSIBLE_TEST_0_3' + multi_control: + purge_action: 'disable' + register: purge3 + failed_when: > + purge3.failed or + not purge3.changed + when: not ansible_check_mode + + - name: Simple disable-purge - nothing changed + oxlorg.opnsense.alias_multi: + multi_purge: + - name: 'ANSIBLE_TEST_0_3' + multi_control: + purge_action: 'disable' + register: purge4 + failed_when: > + purge4.failed or + purge4.changed + when: not ansible_check_mode + + - name: Recreating + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + enabled: false + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + enabled: false + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.12.0/24', '192.168.21.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [8080, 8443] + when: not ansible_check_mode + + - name: Filtered purge (single) + oxlorg.opnsense.alias_multi: + multi_control: + purge_filter: + type: 'port' + when: not ansible_check_mode + register: purge5 + + - name: Listing entries + oxlorg.opnsense.list: + register: opn17 + failed_when: > + 'data' not in opn17 or + opn17.data | length != 4 + when: not ansible_check_mode + + - name: Recreating + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + enabled: false + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + enabled: false + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.12.0/24', '192.168.21.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [8080, 8443] + when: not ansible_check_mode + + - name: Filtered purge (single inverted) + oxlorg.opnsense.alias_multi: + multi_control: + purge_filter: + type: 'port' + purge_filter_invert: true + when: not ansible_check_mode + register: purge6 + failed_when: > + purge6.failed or + not purge6.changed + + - name: Recreating + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_0_1' + type: 'host' + content: '192.168.2.1' + enabled: false + + - name: 'ANSIBLE_TEST_0_2' + type: 'host' + content: ['192.168.2.1', '192.168.2.2'] + enabled: false + + - name: 'ANSIBLE_TEST_0_3' + type: 'network' + content: '192.168.1.0/25' + + - name: 'ANSIBLE_TEST_0_4' + type: 'network' + content: ['192.168.12.0/24', '192.168.21.0/23'] + + - name: 'ANSIBLE_TEST_0_5' + type: 'port' + content: 443 + + - name: 'ANSIBLE_TEST_0_6' + type: 'port' + content: [8080, 8443] + when: not ansible_check_mode + + - name: Filtered purge (partial match) + oxlorg.opnsense.alias_multi: + multi_control: + purge_filter: + content: '192.168.1' + purge_filter_partial: true + when: not ansible_check_mode + register: purge7 + failed_when: > + purge7.failed or + not purge7.changed + + - name: Listing aliases + oxlorg.opnsense.list: + register: opn19 + failed_when: > + 'data' not in opn19 or + opn19.data | length != 4 + when: not ansible_check_mode + + - name: Purging all + oxlorg.opnsense.alias_multi: + multi_control: + purge_all: true + register: opn_clean1 + failed_when: > + opn_clean1.failed or + not opn_clean1.changed + when: not ansible_check_mode + + - name: Checking count + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_version.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_version.yml new file mode 100644 index 0000000..14c53ab --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/1_version.yml @@ -0,0 +1,23 @@ +--- + +- name: System Info + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Query System-Info + oxlorg.opnsense.raw: + url: 'core/firmware/info' + register: system_info + + - name: OPNsense Version + ansible.builtin.debug: + var: system_info.response.product_version diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/README.md b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/README.md new file mode 100644 index 0000000..61dbdf6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/README.md @@ -0,0 +1,140 @@ +# Tests + +## Tester dependencies + +```bash +python3 -m pip install -r requirements_test.txt +``` + +## Firewall dependencies + +The Firewall used for testing should be a dedicated VM for testing. You can use the official [ova image](https://docs.opnsense.org/manual/how-tos/installova.html). + +**THE TESTS WILL OVERRIDE THE EXISTING CONFIG!** + +Most tests fail if some other config is found. + +### Packages + +Some tests need packages to be pre-installed: + +* webproxy_* - `os-squid` +* frr_* - `os-frr` +* bind_* - `os-bind` +* acme_* - `os-acme-client` +* postfix_* - `os-postfix` +* nginx_* - `os-nginx` + +See also: `1_dependencies.yml` + +### Interfaces + +Some tests benefit from having a second network-interface available. + +Add dummy interfaces: +* vtnet1 => assigned as `TEST`/`opt1` (should be in a dedicated layer-2 network) +* vtnet2 => leave unassigned +* vtnet3 => assigned as `TEST2`/`opt2` (interface down) + +You need to add a `opt1` dummy-interface named `TEST`. The assigned IPs do not matter. + +Add another interface and leave it unassigned (`opt2` - `vtnet2`). + +### Internet access + +To perform some tests (system, ids) the test firewall needs to reach some public service: + +* system - `pkg.opnsense.org` +* ids - `rules.emergingthreats.net` + +### Certificates + +These internal certificates need to be created: + +* CA: `OpenVPN` +* Client Certificate: `OpenVPN Client` +* Server Certificate: `OpenVPN Server` - SAN `DNS:openvpn.intern` + +### Gateways + +The gateway tests will not work correctly if the LAN network mismatches. + +You can provide your GW IPs via env-vars: `TEST_FIREWALL_GW1` and `TEST_FIREWALL_GW2` + +The `route` module will expect the gateways `LAN_GW` and `TEST-GW` to exist. + +### Rule interface groups + +The gateway tests will not work correctly if the LAN interface mismatches. + +You can provide your GW lan-if via env-vars: `TEST_FIREWALL_RULE_GRP_IF` + +## DHCRelay + +The DHCRelay tests will not work correctly if the LAN interface mismatches. + +You can provide your lan-if via env-vars: `TEST_DHCRELAY_IF` + + +### LAGG Interfaces + +The LAGG tests will not work correctly if the unassigned interface mismatches. + +You can provide your if via env-vars: `TEST_FIREWALL_LAGG_IF` + +And the count of existing LAGGs via `TEST_FIREWALL_LAGG_CNT` + + +## High Availability + +Tests for `hasync_general` can be run without a backup firewall. However, most of HASync Service tests require +a backup firewall to work. Tests requiring a backup firewall are skipped unless you provide connection details to the +backup firewall. + +You can provide your backup firewall connection via env-vars: + + * `TEST_HASYNC_PEER` + * `TEST_HASYNC_USERNAME` + * `TEST_HASYNC_PASSWORD` + +## Snapshots + +To run snapshot tests - the target firewall needs to be installed with a ZFS filesystem! + +---- + +## Run + +### Single module + +```bash +bash scripts/test_single.sh +> Arguments: +> 1: firewall +> 2: api key file +> 3: path to local collection - set to '0' to clone from github +> 4: name of test to run +> 5: if check-mode should be ran (optional; 0/1; default=1) +> 6: path to virtual environment (optional) +``` + +### All modules + +```bash +bash scripts/test.sh +> Arguments: +> 1: firewall +> 2: api key file +> 3: path to local collection - set to '0' to clone from github +> 4: path to virtual environment (optional) +``` + +---- + +## Automatic tests + +The tests are run automatically using the [OXL infrastructure](https://github.com/O-X-L/ansible-role-oxl-cicd)! + +It is based on [some bash scripts](https://github.com/O-X-L/ansible-role-oxl-cicd/blob/latest/templates/usr/local/bin/cicd/collection_test.sh.j2) and systemd timers. + +Logs for those functional tests can be found here: [Short](https://badges.oss.oxl.app/log/collection_oxlorg.opnsense_test_short.log), [Full](https://badges.oss.oxl.app/log/collection_oxlorg.opnsense_test.log) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/_tmpl.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/_tmpl.yml new file mode 100644 index 0000000..e147e45 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/_tmpl.yml @@ -0,0 +1,144 @@ +--- + +- name: Testing stuff + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + # match_fields: ['description'] + + oxlorg.opnsense.list: + target: '_tmpl_obj' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.list: + + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid value + oxlorg.opnsense.list: + field1: 'INVALID-IP' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid domain + oxlorg.opnsense.list: + field1: '!INVALID-DOMAIN!' + field2: 'INVALID-IP' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.list: + + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.list: + + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.list: + + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.list: + + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.list: + + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.list: + + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.list: + + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.list: + + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.list: + + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_account.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_account.yml new file mode 100644 index 0000000..86ed8fa --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_account.yml @@ -0,0 +1,147 @@ +--- + +- name: Testing ACME Account + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'acme_account' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid ca + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + ca: opnsense + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + ca: letsencrypt_test + email: example@example.com + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + description: letsencrypt_test + ca: letsencrypt_test + email: example@example.com + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + description: letsencrypt_test + ca: letsencrypt_test + email: example@example.com + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + description: letsencrypt_test + ca: letsencrypt_test + email: example@example.com + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + description: letsencrypt_test + ca: letsencrypt_test + email: example@example.com + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + description: letsencrypt_test + ca: letsencrypt_test + email: example@example.com + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_2' + ca: custom + email: example@example.com + custom_ca: 'https://example.com/acme/directory' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.acme_account: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_action.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_action.yml new file mode 100644 index 0000000..98a5e2e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_action.yml @@ -0,0 +1,401 @@ +--- + +- name: Testing ACME Action / Automations + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'acme_action' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid value + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_1' + type: ANSIBLE_TEST_1_1 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid domain + oxlorg.opnsense.acme_action: + field1: '!INVALID-DOMAIN!' + field2: 'INVALID-IP' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_1' + type: configd_restart_gui + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_1' + type: configd_restart_haproxy + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_1' + type: configd_restart_haproxy + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_1' + type: configd_restart_haproxy + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_1' + type: configd_restart_haproxy + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_1' + type: configd_restart_haproxy + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 - Upload SFTP + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_2' + type: configd_upload_sftp + sftp_host: 127.0.0.1 + sftp_user: root + sftp_port: 42 + sftp_identity_type: ecdsa + sftp_remote_path: /tmp + sftp_chgrp: 1000 + sftp_chmod: '0444' + sftp_chmod_key: '0440' + sftp_filename_cert: '%s_cert.pem' + sftp_filename_key: '%s_key.pem' + sftp_filename_ca: '%s_ca.pem' + sftp_filename_fullchain: '%s_chain.pem' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Adding 2 - Upload SFTP - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_2' + type: configd_upload_sftp + sftp_host: 127.0.0.1 + sftp_user: root + sftp_port: 42 + sftp_identity_type: ecdsa + sftp_remote_path: /tmp + sftp_chgrp: 1000 + sftp_chmod: '0444' + sftp_chmod_key: '0440' + sftp_filename_cert: '%s_cert.pem' + sftp_filename_key: '%s_key.pem' + sftp_filename_ca: '%s_ca.pem' + sftp_filename_fullchain: '%s_chain.pem' + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Adding 3 - Remote SSH + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_3' + type: configd_remote_ssh + remote_ssh_host: 127.0.0.1 + remote_ssh_port: 42 + remote_ssh_user: root + remote_ssh_command: date + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Adding 3 - Remote SSH - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_3' + type: configd_remote_ssh + remote_ssh_host: 127.0.0.1 + remote_ssh_port: 42 + remote_ssh_user: root + remote_ssh_command: date + register: opn10 + failed_when: > + opn10.failed or + opn10.changed + when: not ansible_check_mode + + - name: Adding 4 - FRITZ!Box + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_4' + type: acme_fritzbox + acme_fritzbox_url: https://fritzbox.example.com + acme_fritzbox_username: fritz + acme_fritzbox_password: box + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + + - name: Adding 4 - FRITZ!Box - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_4' + type: acme_fritzbox + acme_fritzbox_url: https://fritzbox.example.com + acme_fritzbox_username: fritz + acme_fritzbox_password: box + register: opn12 + failed_when: > + opn12.failed or + opn12.changed + when: not ansible_check_mode + + - name: Adding 5 - PANOS + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_5' + type: acme_panos + acme_panos_username: palo + acme_panos_password: alto + acme_panos_host: fw + register: opn13 + failed_when: > + opn13.failed or + not opn13.changed + + - name: Adding 5 - PANOS - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_5' + type: acme_panos + acme_panos_username: palo + acme_panos_password: alto + acme_panos_host: fw + register: opn14 + failed_when: > + opn14.failed or + opn14.changed + when: not ansible_check_mode + + - name: Adding 6 - Proxmox + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_6' + type: acme_proxmoxve + acme_proxmoxve_user: prox + acme_proxmoxve_server: prox1 + acme_proxmoxve_port: 8007 + acme_proxmoxve_nodename: prox1a + acme_proxmoxve_realm: ldap + acme_proxmoxve_tokenid: opnacme + acme_proxmoxve_tokenkey: SECRET + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + + - name: Adding 6 - Proxmox - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_6' + type: acme_proxmoxve + acme_proxmoxve_user: prox + acme_proxmoxve_server: prox1 + acme_proxmoxve_port: 8007 + acme_proxmoxve_nodename: prox1a + acme_proxmoxve_realm: ldap + acme_proxmoxve_tokenid: opnacme + acme_proxmoxve_tokenkey: SECRET + register: opn16 + failed_when: > + opn16.failed or + opn16.changed + when: not ansible_check_mode + + - name: Adding 7 - Vault + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_7' + type: acme_vault + acme_vault_url: http://vault.example.com:8200. + acme_vault_prefix: opnacme + acme_vault_token: SECRET + acme_vault_kvv2: true + register: opn17 + failed_when: > + opn17.failed or + not opn17.changed + + - name: Adding 7 - Vault - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_7' + type: acme_vault + acme_vault_url: http://vault.example.com:8200. + acme_vault_prefix: opnacme + acme_vault_token: SECRET + acme_vault_kvv2: true + register: opn18 + failed_when: > + opn18.failed or + opn18.changed + when: not ansible_check_mode + + - name: Adding 8 - Synology DSM + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_8' + type: acme_synology_dsm + acme_synology_dsm_hostname: synology.example.com + acme_synology_dsm_port: 5042 + acme_synology_dsm_scheme: https + acme_synology_dsm_username: synology + acme_synology_dsm_password: DSM + acme_synology_dsm_create: true + acme_synology_dsm_deviceid: id1 + acme_synology_dsm_devicename: name1 + register: opn19 + failed_when: > + opn19.failed or + not opn19.changed + + - name: Adding 8 - Synology DSM - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_8' + type: acme_synology_dsm + acme_synology_dsm_hostname: synology.example.com + acme_synology_dsm_port: 5042 + acme_synology_dsm_scheme: https + acme_synology_dsm_username: synology + acme_synology_dsm_password: DSM + acme_synology_dsm_create: true + acme_synology_dsm_deviceid: id1 + acme_synology_dsm_devicename: name1 + register: opn20 + failed_when: > + opn20.failed or + opn20.changed + when: not ansible_check_mode + + - name: Adding 9 - TrueNAS + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_9' + type: acme_truenas + acme_truenas_apikey: SECRET + acme_truenas_hostname: truenas.example.com + acme_truenas_scheme: https + register: opn21 + failed_when: > + opn21.failed or + not opn21.changed + + - name: Adding 9 - TrueNAS - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_9' + type: acme_truenas + acme_truenas_apikey: SECRET + acme_truenas_hostname: truenas.example.com + acme_truenas_scheme: https + register: opn22 + failed_when: > + opn22.failed or + opn22.changed + when: not ansible_check_mode + + - name: Adding 10 - Unifi + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_10' + type: acme_unifi + acme_unifi_keystore: /tmp/keystore + register: opn23 + failed_when: > + opn23.failed or + not opn23.changed + + - name: Adding 10 - Unifi - nothing changed + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_1_10' + type: acme_unifi + acme_unifi_keystore: /tmp/keystore + register: opn24 + failed_when: > + opn24.failed or + opn24.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 10 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.acme_action: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + - 'ANSIBLE_TEST_1_5' + - 'ANSIBLE_TEST_1_6' + - 'ANSIBLE_TEST_1_7' + - 'ANSIBLE_TEST_1_8' + - 'ANSIBLE_TEST_1_9' + - 'ANSIBLE_TEST_1_10' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_certificate.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_certificate.yml new file mode 100644 index 0000000..c69e08c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_certificate.yml @@ -0,0 +1,334 @@ +--- + +- name: Testing ACME Certificate + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'acme_certificate' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.acme_certificate: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding dummy account + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_DUMMY_1_1' + ca: letsencrypt_test + + - name: Adding dummy validation + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_DUMMY_1_1' + method: http01 + + - name: Adding dummy action + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_DUMMY_1_1' + type: configd_restart_gui + + - name: Adding 1 - failing because of missing name + oxlorg.opnsense.acme_certificate: + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of missing account + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of unknown account + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'DOES_NOT_EXIST' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 - failing because of missing validation + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding 1 - failing because of unknown validation + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'DOES_NOT_EXIST' + register: opn_fail5 + failed_when: not opn_fail5.failed + + - name: Adding 1 - failing because of unknown restart_actions + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + restart_actions: + - 'DOES_NOT_EXIST' + register: opn_fail6 + failed_when: not opn_fail6.failed + + - name: Adding 1 + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.acme_certificate: + name: 'example.com' + alt_names: + - 'sub.example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + restart_actions: + - 'ANSIBLE_TEST_DUMMY_1_1' + auto_renewal: false + renew_interval: 42 + key_length: key_ec384 + ocsp: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.acme_certificate: + name: 'example.com' + alt_names: + - 'sub.example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + restart_actions: + - 'ANSIBLE_TEST_DUMMY_1_1' + auto_renewal: false + renew_interval: 42 + key_length: key_ec384 + ocsp: true + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + + - name: Disabling 1 + oxlorg.opnsense.acme_certificate: + name: 'example.com' + alt_names: + - 'sub.example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + restart_actions: + - 'ANSIBLE_TEST_DUMMY_1_1' + auto_renewal: false + renew_interval: 42 + key_length: key_ec384 + ocsp: true + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.acme_certificate: + name: 'example.com' + alt_names: + - 'sub.example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + restart_actions: + - 'ANSIBLE_TEST_DUMMY_1_1' + auto_renewal: false + renew_interval: 42 + key_length: key_ec384 + ocsp: true + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.acme_certificate: + name: 'example.com' + alt_names: + - 'sub.example.com' + description: 'ANSIBLE_TEST_1_1' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + restart_actions: + - 'ANSIBLE_TEST_DUMMY_1_1' + auto_renewal: false + renew_interval: 42 + key_length: key_ec384 + ocsp: true + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_2' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + aliasmode: domain + domainalias: aliasDomainForValidationOnly.com + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Changing 2 + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_2' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + aliasmode: domain + domainalias: aliasDomainForValidationOnly2.com + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + + - name: Changing 2 - nothing changed + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_2' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + aliasmode: domain + domainalias: aliasDomainForValidationOnly2.com + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + + - name: Adding 3 + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_3' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + aliasmode: challenge + challengealias: aliasDomainForValidationOnly.com + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + + - name: Changing 3 + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_3' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + aliasmode: challenge + challengealias: aliasDomainForValidationOnly2.com + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + + - name: Changing 3 - nothing changed + oxlorg.opnsense.acme_certificate: + name: 'example.com' + description: 'ANSIBLE_TEST_1_3' + account: 'ANSIBLE_TEST_DUMMY_1_1' + validation: 'ANSIBLE_TEST_DUMMY_1_1' + aliasmode: challenge + challengealias: aliasDomainForValidationOnly2.com + register: opn12 + failed_when: > + opn12.failed or + opn12.changed + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 3 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.acme_certificate: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + + - name: Cleanup Dummy Account + oxlorg.opnsense.acme_account: + name: 'ANSIBLE_TEST_DUMMY_1_1' + state: 'absent' + diff: false + + - name: Cleanup Dummy Validation + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_DUMMY_1_1' + state: 'absent' + diff: false + + - name: Cleanup Dummy Action + oxlorg.opnsense.acme_action: + name: 'ANSIBLE_TEST_DUMMY_1_1' + state: 'absent' + diff: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_general.yml new file mode 100644 index 0000000..b3f4f7c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_general.yml @@ -0,0 +1,90 @@ +--- + +- name: Testing ACME Setting + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'acme_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid challenge_port + oxlorg.opnsense.acme_general: + challenge_port: 10 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid tls_challenge_port + oxlorg.opnsense.acme_general: + tls_challenge_port: 10 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring - failing because of invalid restart_timeout + oxlorg.opnsense.acme_general: + restart_timeout: 100000 + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Configuring + oxlorg.opnsense.acme_general: + enabled: true + auto_renewal: false + challenge_port: 1042 + tls_challenge_port: 1043 + restart_timeout: 642 + log_level: extended + show_intro: false + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.acme_general: + enabled: false + auto_renewal: true + challenge_port: 43580 + tls_challenge_port: 43581 + restart_timeout: 600 + log_level: normal + show_intro: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.acme_general: + enabled: false + auto_renewal: true + challenge_port: 43580 + tls_challenge_port: 43581 + restart_timeout: 600 + log_level: normal + show_intro: true + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.acme_general: + enabled: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_validation.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_validation.yml new file mode 100644 index 0000000..d93bb9c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/acme_validation.yml @@ -0,0 +1,361 @@ +--- + +- name: Testing ACME Validation + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'acme_validation' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid value + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + method: ANSIBLE_TEST_1_1 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + method: http01 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + method: http01 + http_opn_autodiscovery: false + http_opn_ipaddresses: + - 1.2.3.4 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + method: http01 + http_opn_autodiscovery: false + http_opn_ipaddresses: + - 1.2.3.4 + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + method: http01 + http_opn_autodiscovery: false + http_opn_ipaddresses: + - 1.2.3.4 + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + method: http01 + http_opn_autodiscovery: false + http_opn_ipaddresses: + - 1.2.3.4 + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + method: http01 + http_opn_autodiscovery: false + http_opn_ipaddresses: + - 1.2.3.4 + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 - HTTP HAProxy + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_2' + method: http01 + http_service: haproxy + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Adding 2 - HTTP HAProxy - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_2' + method: http01 + http_service: haproxy + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Adding 3 - TLS ALPN + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_3' + method: tlsalpn01 + tlsalpn_acme_ipaddresses: + - 1.2.3.4 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Adding 3 - TLS ALPN - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_3' + method: tlsalpn01 + tlsalpn_acme_ipaddresses: + - 1.2.3.4 + register: opn10 + failed_when: > + opn10.failed or + opn10.changed + when: not ansible_check_mode + + - name: Adding 4 - DNS Active24 + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_4' + dns_service: dns_active24 + dns_active24_token: SECRET + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + + - name: Adding 4 - DNS Active24 - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_4' + dns_service: dns_active24 + dns_active24_token: SECRET + register: opn12 + failed_when: > + opn12.failed or + opn12.changed + when: not ansible_check_mode + + - name: Adding 5 - DNS aliyun.com + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_5' + dns_service: dns_ali + dns_ali_key: KEY + dns_ali_secret: SECRET + register: opn13 + failed_when: > + opn13.failed or + not opn13.changed + + - name: Adding 5 - DNS aliyun.com - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_5' + dns_service: dns_ali + dns_ali_key: KEY + dns_ali_secret: SECRET + register: opn14 + failed_when: > + opn14.failed or + opn14.changed + when: not ansible_check_mode + + - name: Adding 6 - DNS AutoDNS + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_6' + dns_service: dns_autodns + dns_autodns_user: auto + dns_autodns_password: SECRET + dns_autodns_context: OPN + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + + - name: Adding 6 - DNS AutoDNS - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_6' + dns_service: dns_autodns + dns_autodns_user: auto + dns_autodns_password: SECRET + dns_autodns_context: OPN + register: opn16 + failed_when: > + opn16.failed or + opn16.changed + when: not ansible_check_mode + + - name: Adding 7 - DNS AWS + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_7' + dns_service: dns_aws + dns_aws_id: ID + dns_aws_secret: SECRET + register: opn17 + failed_when: > + opn17.failed or + not opn17.changed + + - name: Adding 7 - DNS AWS - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_7' + dns_service: dns_aws + dns_aws_id: ID + dns_aws_secret: SECRET + register: opn18 + failed_when: > + opn18.failed or + opn18.changed + when: not ansible_check_mode + + - name: Adding 8 - DNS Azure + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_8' + dns_service: dns_azure + dns_azuredns_subscriptionid: SUB + dns_azuredns_tenantid: TENANT + dns_azuredns_appid: APP + dns_azuredns_clientsecret: SECRET + register: opn19 + failed_when: > + opn19.failed or + not opn19.changed + + - name: Adding 8 - DNS Azure - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_8' + dns_service: dns_azure + dns_azuredns_subscriptionid: SUB + dns_azuredns_tenantid: TENANT + dns_azuredns_appid: APP + dns_azuredns_clientsecret: SECRET + register: opn20 + failed_when: > + opn20.failed or + opn20.changed + when: not ansible_check_mode + + - name: Adding 9 - DNS Bunny + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_9' + dns_service: dns_bunny + dns_bunny_api_key: SECRET + register: opn21 + failed_when: > + opn21.failed or + not opn21.changed + + - name: Adding 9 - DNS Bunny - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_9' + dns_service: dns_bunny + dns_bunny_api_key: SECRET + register: opn22 + failed_when: > + opn22.failed or + opn22.changed + when: not ansible_check_mode + + - name: Adding 10 - DNS Cloudflare + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_10' + dns_service: dns_cf + dns_cf_email: cf@example.com + dns_cf_key: KEY + dns_cf_token: SECRET + dns_cf_account_id: Account + dns_cf_zone_id: ZONE + register: opn23 + failed_when: > + opn23.failed or + not opn23.changed + + - name: Adding 10 - Unifi - nothing changed + oxlorg.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_10' + dns_service: dns_cf + dns_cf_email: cf@example.com + dns_cf_key: KEY + dns_cf_token: SECRET + dns_cf_account_id: Account + dns_cf_zone_id: ZONE + register: opn24 + failed_when: > + opn24.failed or + opn24.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 10 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.acme_validation: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + - 'ANSIBLE_TEST_1_5' + - 'ANSIBLE_TEST_1_6' + - 'ANSIBLE_TEST_1_7' + - 'ANSIBLE_TEST_1_8' + - 'ANSIBLE_TEST_1_9' + - 'ANSIBLE_TEST_1_10' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias.yml new file mode 100644 index 0000000..abe9660 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias.yml @@ -0,0 +1,520 @@ +--- + +- name: Testing Alias - stati + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.alias: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Removing - does not exist + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn1 + failed_when: opn1.changed + + - name: Failing because alias name is too long (>32 char) + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1_TOOOOOOOOOOO_LONG' + content: '1.1.1.1' + register: opn13 + failed_when: not opn13.failed + + - name: Adding + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + content: '1.1.1.1' + register: opn2 + failed_when: > + not opn2.changed or + opn2.failed + + - name: Nothing changed + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + content: '1.1.1.1' + register: opn3 + failed_when: > + opn3.changed or + opn3.failed + when: not ansible_check_mode + + - name: Changing + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + content: ['1.1.1.2', '1.1.1.3'] + register: opn4 + failed_when: > + not opn4.changed or + opn4.failed + + - name: Changing to other type - invalid + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + type: 'url' + content: 'https://test.oxlorg.net' + register: opn9 + failed_when: not opn9.failed + when: not ansible_check_mode + + - name: Disabling + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + enabled: false + content: ['1.1.1.2', '1.1.1.3'] + register: opn5 + failed_when: > + not opn5.changed or + opn5.failed + when: not ansible_check_mode + + - name: Disabling - nothing changed + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + enabled: false + content: ['1.1.1.2', '1.1.1.3'] + register: opn8 + failed_when: > + opn8.changed or + opn8.failed + when: not ansible_check_mode + + - name: Enabling + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + content: ['1.1.1.2', '1.1.1.3'] + register: opn6 + failed_when: > + not opn6.changed or + opn6.failed + when: not ansible_check_mode + + - name: Enabling - nothing changed + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + content: ['1.1.1.2', '1.1.1.3'] + register: opn7 + failed_when: > + opn7.changed or + opn7.failed + when: not ansible_check_mode + + - name: Removing + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn4 + failed_when: > + not opn4.changed or + opn4.failed + when: not ansible_check_mode + +- name: Testing Alias - types + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.alias: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + # hosts + + - name: Adding single host + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_HOST1' + type: 'host' + content: '1.1.1.1' + + - name: Adding multiple hosts + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_HOST2' + type: 'host' + content: ['1.1.1.1', '1.1.1.2'] + + - name: Adding nested host + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_HOST3' + type: 'host' + content: ['ANSIBLE_TEST_1_2_HOST2', 'ANSIBLE_TEST_1_2_HOST1'] + register: opnhost2 + failed_when: opnhost2.failed != ansible_check_mode + + - name: Adding ip range + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_HOST4' + type: 'host' + content: '1.1.1.1-1.1.1.4' + + - name: Adding exclusion + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_HOST5' + type: 'host' + content: '!1.1.1.1' + + - name: Adding invalid host + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_HOST3' + type: 'host' + content: "{{ item }}" + register: opnhost1 + failed_when: not opnhost1.failed + loop: + - ['1.1.1.n'] + + # networks + + - name: Adding network + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_NET1' + type: 'network' + content: '192.168.0.0/24' + + - name: Adding multiple networks + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_NET2' + type: 'network' + content: ['192.168.0.0/24', '192.168.1.0/24'] + + - name: Adding invalid network + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_NET3' + type: 'network' + content: "{{ item }}" + register: opnnet1 + failed_when: not opnnet1.failed + loop: + - ['192.168.0.0/240'] + + - name: Adding nested network + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_NET3' + type: 'network' + content: ['ANSIBLE_TEST_1_2_NET1', 'ANSIBLE_TEST_1_2_NET2'] + register: opnnet2 + failed_when: opnnet2.failed != ansible_check_mode + + # ports + + - name: Adding port + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_PORT1' + type: 'port' + content: 9000 + + - name: Adding multiple ports + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_PORT2' + type: 'port' + content: ['80', 443] + + - name: Adding invalid port + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_PORT3' + type: 'port' + content: "{{ item }}" + register: opnport1 + failed_when: not opnport1.failed + loop: + - ['70000'] + + - name: Adding port range + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_PORT4' + type: 'port' + content: '9000:9500' + + - name: Adding nested ports + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_PORT5' + type: 'port' + content: + - 'ANSIBLE_TEST_1_2_PORT1' + - 'ANSIBLE_TEST_1_2_PORT2' + register: opnport2 + failed_when: opnport2.failed != ansible_check_mode + + - name: Adding port service name + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_PORT6' + type: 'port' + content: 'smtp' + + # mac + + - name: Adding mac + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_MAC1' + type: 'mac' + content: f4:90:ea:00:00:01 + + - name: Adding multiple macs + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_MAC2' + type: 'mac' + content: ['f4:90:ea:00:00:01', 'f4:90:ea:00:00:02'] + + - name: Adding partial mac + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_MAC3' + type: 'mac' + content: f4:90:ea:00 + + - name: Adding invalid mac + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_MAC4' + type: 'mac' + content: "{{ item }}" + register: opnmac1 + failed_when: not opnmac1.failed + loop: + - ['aa:bb:cc:dd:ee:gg'] + - ['aa:bb:cc:dd:ee:fff'] + - ['aa:bb:cc:dd:ee:ff:ff'] + - ['aa:bb:cc:dd:ee:f'] + - ['aa:bb:cc:dd:ee:'] + - ['aa:'] + + # urls + + - name: Adding url + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URL1' + type: 'url' + content: 'https://oxlorg.net' + + - name: Adding multiple urls + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URL2' + type: 'url' + content: ['https://template.oxlorg.net', 'http://test.oxlorg.net'] + + - name: Adding invalid url + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URL3' + type: 'url' + content: "{{ item }}" + register: opnurl1 + failed_when: not opnurl1.failed + loop: + - ['httks://test.oxlorg.net'] + - ['test.oxlorg.net'] + + # urltable + + - name: Adding urltable + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URLTABLE1' + type: 'urltable' + updatefreq_days: 2 + content: 'https://www.spamhaus.org/drop/drop.txt' + + - name: Adding urltable - nothing changed + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URLTABLE1' + type: 'urltable' + updatefreq_days: 2 + content: 'https://www.spamhaus.org/drop/drop.txt' + register: opn14 + failed_when: > + opn14.failed or + opn14.changed + when: not ansible_check_mode + + - name: Updating urltable update-frequency + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URLTABLE1' + type: 'urltable' + updatefreq_days: 6.5 + content: 'https://www.spamhaus.org/drop/drop.txt' + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + when: not ansible_check_mode + + - name: Adding multiple urltables + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URLTABLE2' + type: 'urltable' + content: ['https://www.spamhaus.org/drop/drop.txt'] + + - name: Adding invalid urltable + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URLTABLE3' + type: 'urltable' + content: "{{ item }}" + register: opnurltable1 + failed_when: not opnurltable1.failed + loop: + - ['httks://test.oxlorg.net'] + # - ['https://oxlorg.net'] # not an urltable + + # urljson + + - name: Adding urljson + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URLJSON1' + type: 'urltable' + updatefreq_days: 2 + content: 'https://api.github.com/meta' + path_expression: .web + .api + .git | .[] + + - name: Adding urljson - nothing changed + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_URLJSON1' + type: 'urltable' + updatefreq_days: 2 + content: 'https://api.github.com/meta' + path_expression: .web + .api + .git | .[] + register: opn16 + failed_when: > + opn16.failed or + opn16.changed + when: not ansible_check_mode + + # geoip + + - name: Adding geoip + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_GEOIP1' + type: 'geoip' + content: 'AT' + + - name: Adding multiple geoips + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_GEOIP2' + type: 'geoip' + content: ['AT', 'DE', 'CH'] + + - name: Adding invalid geoip + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_GEOIP3' + type: 'geoip' + content: "{{ item }}" + register: opngeoip1 + failed_when: not opngeoip1.failed + when: not ansible_check_mode # no client-side validation + loop: + - ['XXX'] + + # dynipv6host + + - name: Adding dynipv6host + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_2_DYNIPV6HOST1' + type: 'dynipv6host' + content: + - '::1000' + - '::f00d' + interface: 'lan' + + # cleanup + + - name: Cleanup + oxlorg.opnsense.alias: + name: "{{ item }}" + state: 'absent' + diff: false + loop: + - 'ANSIBLE_TEST_1_2_URLJSON1' + - 'ANSIBLE_TEST_1_2_URLTABLE3' + - 'ANSIBLE_TEST_1_2_URLTABLE2' + - 'ANSIBLE_TEST_1_2_URLTABLE1' + - 'ANSIBLE_TEST_1_2_URL1' + - 'ANSIBLE_TEST_1_2_URL2' + - 'ANSIBLE_TEST_1_2_URL3' + - 'ANSIBLE_TEST_1_2_PORT5' + - 'ANSIBLE_TEST_1_2_PORT1' + - 'ANSIBLE_TEST_1_2_PORT2' + - 'ANSIBLE_TEST_1_2_PORT3' + - 'ANSIBLE_TEST_1_2_PORT4' + - 'ANSIBLE_TEST_1_2_PORT6' + - 'ANSIBLE_TEST_1_2_MAC1' + - 'ANSIBLE_TEST_1_2_MAC2' + - 'ANSIBLE_TEST_1_2_MAC3' + - 'ANSIBLE_TEST_1_2_MAC4' + - 'ANSIBLE_TEST_1_2_NET3' + - 'ANSIBLE_TEST_1_2_NET1' + - 'ANSIBLE_TEST_1_2_NET2' + - 'ANSIBLE_TEST_1_2_NET4' + - 'ANSIBLE_TEST_1_2_HOST5' + - 'ANSIBLE_TEST_1_2_HOST4' + - 'ANSIBLE_TEST_1_2_HOST3' + - 'ANSIBLE_TEST_1_2_HOST2' + - 'ANSIBLE_TEST_1_2_HOST1' + - 'ANSIBLE_TEST_1_2_GEOIP1' + - 'ANSIBLE_TEST_1_2_GEOIP2' + - 'ANSIBLE_TEST_1_2_GEOIP3' + - 'ANSIBLE_TEST_1_2_DYNIPV6HOST1' + +- name: Testing Alias - listing + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'alias' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing aliases (none) + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Adding dummy alias + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_3_1' + content: 'test.oxlorg.net' + when: not ansible_check_mode + + - name: Listing aliases + oxlorg.opnsense.list: + register: opn11 + failed_when: > + 'data' not in opn11 or + opn11.data | length != 1 + when: not ansible_check_mode + + - name: Adding dummy alias 2 + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_1_3_2' + content: 'template.oxlorg.net' + when: not ansible_check_mode + + - name: Listing aliases + oxlorg.opnsense.list: + register: opn12 + failed_when: > + 'data' not in opn12 or + opn12.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.alias: + name: "{{ item }}" + state: 'absent' + diff: false + loop: + - 'ANSIBLE_TEST_1_3_1' + - 'ANSIBLE_TEST_1_3_2' diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias_multi.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias_multi.yml new file mode 100644 index 0000000..f646538 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias_multi.yml @@ -0,0 +1,255 @@ +--- + +- name: Testing Multiple Aliases + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'alias' + + oxlorg.opnsense.alias_multi: + multi_control: + fail_verify: true + fail_process: true + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Fail required args (1) + oxlorg.opnsense.alias_multi: + register: opn8 + failed_when: not opn8.failed + + - name: Removing - do not exist + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_2_1' + - name: 'ANSIBLE_TEST_2_2' + - name: 'ANSIBLE_TEST_2_3' + - name: 'ANSIBLE_TEST_2_4' + - name: 'ANSIBLE_TEST_2_5' + - name: 'ANSIBLE_TEST_2_6' + - name: 'ANSIBLE_TEST_2_7' + - name: 'ANSIBLE_TEST_2_8' + - name: 'ANSIBLE_TEST_2_9' + - name: 'ANSIBLE_TEST_2_10' + - name: 'ANSIBLE_TEST_2_11' + - name: 'ANSIBLE_TEST_2_12' + - name: 'ANSIBLE_TEST_2_13' + - name: 'ANSIBLE_TEST_2_14' + multi_control: + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding + oxlorg.opnsense.alias_multi: + aliases: + # hosts + - name: 'ANSIBLE_TEST_2_1' + content: '192.168.1.1' + - name: 'ANSIBLE_TEST_2_2' + content: ['192.168.1.1', '192.168.1.2'] + + # networks + - name: 'ANSIBLE_TEST_2_3' + type: 'network' + content: '192.168.1.0/24' + - name: 'ANSIBLE_TEST_2_4' + type: 'network' + content: ['192.168.1.0/24', '192.168.2.0/24'] + + # ports + - name: 'ANSIBLE_TEST_2_5' + type: 'port' + content: 80 + - name: 'ANSIBLE_TEST_2_6' + type: 'port' + content: [80, 443] + + # urls + - name: 'ANSIBLE_TEST_2_7' + type: 'url' + content: 'http://test.oxlorg.net' + + # urltables + - name: 'ANSIBLE_TEST_2_8' + type: 'urltable' + content: ['https://www.spamhaus.org/drop/drop.txt'] + updatefreq_days: 6.5 + + # geoip + - name: 'ANSIBLE_TEST_2_9' + type: 'geoip' + content: 'AT' + - name: 'ANSIBLE_TEST_2_10' + type: 'geoip' + content: ['AT', 'DE', 'CH'] + + # dynipv6host + - name: 'ANSIBLE_TEST_2_11' + type: 'dynipv6host' + content: + - '::f00d' + interface: 'lan' + + reload: false # geoip and urltable take LONG time + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_2_2' + content: ['192.168.1.1', '192.168.1.3'] + + - name: 'ANSIBLE_TEST_2_3' + type: 'network' + content: '192.168.10.0/24' + + - name: 'ANSIBLE_TEST_2_5' + type: 'port' + content: 81 + + - name: 'ANSIBLE_TEST_2_7' + type: 'url' + content: 'http://test.template.oxlorg.net' + + - name: 'ANSIBLE_TEST_2_8' + type: 'urltable' + content: 'https://www.spamhaus.org/drop/dropv6.txt' + updatefreq_days: 2 + + - name: 'ANSIBLE_TEST_2_9' + type: 'geoip' + content: 'DE' + + - name: 'ANSIBLE_TEST_2_11' + type: 'dynipv6host' + content: + - '::1000' + - '::f00d' + interface: 'lan' + reload: false # geoip and urltable take LONG time + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_2_2' + content: ['192.168.1.1', '192.168.1.3'] + + - name: 'ANSIBLE_TEST_2_3' + type: 'network' + content: '192.168.10.0/24' + + - name: 'ANSIBLE_TEST_2_5' + type: 'port' + content: 81 + + - name: 'ANSIBLE_TEST_2_7' + type: 'url' + content: 'http://test.template.oxlorg.net' + + - name: 'ANSIBLE_TEST_2_8' + type: 'urltable' + updatefreq_days: 2 + content: 'https://www.spamhaus.org/drop/dropv6.txt' + + - name: 'ANSIBLE_TEST_2_9' + type: 'geoip' + content: 'DE' + + - name: 'ANSIBLE_TEST_2_11' + type: 'dynipv6host' + content: + - '::1000' + - '::f00d' + interface: 'lan' + reload: false # geoip and urltable take LONG time + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Adding nested aliases + oxlorg.opnsense.alias_multi: + aliases: + # hosts + - name: 'ANSIBLE_TEST_2_12' + content: ['ANSIBLE_TEST_2_1', 'ANSIBLE_TEST_2_2'] + + # networks + - name: 'ANSIBLE_TEST_2_13' + type: 'network' + content: ['ANSIBLE_TEST_2_3', 'ANSIBLE_TEST_2_4'] + + # ports + - name: 'ANSIBLE_TEST_2_14' + type: 'port' + content: ['ANSIBLE_TEST_2_5', 'ANSIBLE_TEST_2_6'] + + reload: false # geoip and urltable take LONG time + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing aliases + oxlorg.opnsense.list: + register: opn4 + failed_when: > + 'data' not in opn4 or + opn4.data | length != 14 + when: not ansible_check_mode + + - name: Removing nesting + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_2_12' + - name: 'ANSIBLE_TEST_2_13' + - name: 'ANSIBLE_TEST_2_14' + multi_control: + state: 'absent' + when: not ansible_check_mode + + - name: Removing + oxlorg.opnsense.alias_multi: + aliases: + - name: 'ANSIBLE_TEST_2_1' + - name: 'ANSIBLE_TEST_2_2' + - name: 'ANSIBLE_TEST_2_3' + - name: 'ANSIBLE_TEST_2_4' + - name: 'ANSIBLE_TEST_2_5' + - name: 'ANSIBLE_TEST_2_6' + - name: 'ANSIBLE_TEST_2_7' + - name: 'ANSIBLE_TEST_2_8' + - name: 'ANSIBLE_TEST_2_9' + - name: 'ANSIBLE_TEST_2_10' + - name: 'ANSIBLE_TEST_2_11' + multi_control: + state: 'absent' + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn5 + failed_when: > + 'data' not in opn5 or + opn5.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias_purge.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias_purge.yml new file mode 100644 index 0000000..5d3e22f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/alias_purge.yml @@ -0,0 +1,197 @@ +--- + +- name: Testing Purging of Aliases + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'alias' + + oxlorg.opnsense.alias_multi: + multi_control: + fail_verify: true + fail_process: true + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Purge ALL + oxlorg.opnsense.alias_multi: + multi_control: + purge_all: true + + - name: Adding 1 + oxlorg.opnsense.alias_multi: + multi: + - name: 'ANSIBLE_TEST_4_1' + content: ['192.168.1.1', '192.168.1.2'] + + - name: 'ANSIBLE_TEST_4_2' + type: 'network' + content: '192.168.1.0/24' + + - name: 'ANSIBLE_TEST_4_3' + type: 'port' + content: [80, 443] + when: not ansible_check_mode + + - name: Simple purge + oxlorg.opnsense.alias_multi: + multi_purge: + - name: 'ANSIBLE_TEST_4_1' + when: not ansible_check_mode + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Listing aliases + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 2 + when: not ansible_check_mode + + - name: Simple disable-purge + oxlorg.opnsense.alias_multi: + multi_purge: + - name: 'ANSIBLE_TEST_4_2' + multi_control: + purge_action: 'disable' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Simple disable-purge - nothing changed + oxlorg.opnsense.alias_multi: + multi_purge: + - name: 'ANSIBLE_TEST_4_2' + multi_control: + purge_action: 'disable' + register: opn11 + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.alias_multi: + multi: + - name: 'ANSIBLE_TEST_4_1' + content: ['192.168.1.1', '192.168.1.2'] + + - name: 'ANSIBLE_TEST_4_2' + type: 'network' + content: '192.168.1.0/24' + + - name: 'ANSIBLE_TEST_4_3' + type: 'port' + content: [80, 443] + when: not ansible_check_mode + + - name: Filtered purge (single) + oxlorg.opnsense.alias_multi: + multi_control: + purge_filter: + type: 'port' + when: not ansible_check_mode + register: opn4 + + - name: Listing aliases + oxlorg.opnsense.list: + register: opn5 + failed_when: > + 'data' not in opn5 or + opn5.data | length != 2 + when: not ansible_check_mode + + - name: Adding 3 + oxlorg.opnsense.alias_multi: + multi: + - name: 'ANSIBLE_TEST_4_1' + content: ['192.168.1.1', '192.168.1.2'] + + - name: 'ANSIBLE_TEST_4_2' + type: 'network' + content: '192.168.1.0/24' + + - name: 'ANSIBLE_TEST_4_3' + type: 'port' + content: [80, 443] + when: not ansible_check_mode + + - name: Filtered purge (single inverted) + oxlorg.opnsense.alias_multi: + multi_control: + purge_filter: + type: 'port' + purge_filter_invert: true + when: not ansible_check_mode + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Listing aliases + oxlorg.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 1 + when: not ansible_check_mode + + - name: Adding 4 + oxlorg.opnsense.alias_multi: + multi: + - name: 'ANSIBLE_TEST_4_1' + content: ['192.168.1.1', '192.168.1.2'] + + - name: 'ANSIBLE_TEST_4_2' + content: 'ANSIBLE_TEST_4_1' + when: not ansible_check_mode + + - name: Purge used alias + oxlorg.opnsense.alias_multi: + multi_purge: + - name: 'ANSIBLE_TEST_4_1' + multi_control: + fail_process: true # default + when: not ansible_check_mode + register: opn11 + failed_when: > + not opn11.failed or + 'currently referenced' not in opn11.msg + + - name: Purge ALL (except referenced by other) + oxlorg.opnsense.alias_multi: + multi_control: + purge_all: true + when: not ansible_check_mode + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Purge ALL (previously referenced) + oxlorg.opnsense.alias_multi: + multi_control: + purge_all: true + when: not ansible_check_mode + register: opn12 + failed_when: > + opn12.failed or + not opn12.changed + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_acl.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_acl.yml new file mode 100644 index 0000000..a1d114a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_acl.yml @@ -0,0 +1,153 @@ +--- + +- name: Testing BIND ACLs + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'bind_acl' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 or + opn_pre1.data | length > 0 + + - name: Adding - failing because of invalid names + oxlorg.opnsense.bind_acl: + name: "{{ item }}" + networks: ['192.168.0.0/24'] + register: opn_fail1 + failed_when: not opn_fail1.failed + loop: + - 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + - 'localhost' + + - name: Adding - failing because no network was provided + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_1' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding - failing because an invalid network was provided + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_1' + networks: ['192.168.0.0/258'] + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_1' + networks: ['192.168.0.0/24'] + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_1' + networks: ['192.168.0.0/25'] + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_1' + networks: ['192.168.0.0/25'] + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_1' + networks: ['192.168.0.0/25'] + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_1' + networks: ['192.168.0.0/25'] + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_2' + networks: ['192.168.0.128/29', '192.168.0.192/29'] + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 - Nothing changed + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_2' + networks: ['192.168.0.128/29', '192.168.0.192/29'] + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.bind_acl: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.bind_acl: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_blocklist.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_blocklist.yml new file mode 100644 index 0000000..f9804a5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_blocklist.yml @@ -0,0 +1,113 @@ +--- + +- name: Testing BIND blocklists + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'bind_blocklist' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring + oxlorg.opnsense.bind_blocklist: + block: ['Steven Black List', 'AdGuard List'] + exclude: 'test.oxlorg.net' + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.bind_blocklist: + block: ['Steven Black List', 'NoCoin List', 'Blocklist.site Phishing', 'AdGuard List'] + exclude: ['test.oxlorg.net', 'oxlorg.net'] + safe_google: true + enabled: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.bind_blocklist: + block: ['Steven Black List', 'NoCoin List', 'Blocklist.site Phishing', 'AdGuard List'] + exclude: ['test.oxlorg.net', 'oxlorg.net'] + safe_google: true + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.bind_blocklist: + block: ['Steven Black List', 'NoCoin List', 'Blocklist.site Phishing', 'AdGuard List'] + exclude: ['test.oxlorg.net', 'oxlorg.net'] + safe_google: true + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.bind_blocklist: + block: ['Steven Black List', 'NoCoin List', 'Blocklist.site Phishing', 'AdGuard List'] + exclude: ['test.oxlorg.net', 'oxlorg.net'] + safe_google: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Changing more + oxlorg.opnsense.bind_blocklist: + block: ['Steven Black List', 'NoCoin List', 'Blocklist.site Phishing', 'AdGuard List'] + exclude: ['test.oxlorg.net', 'oxlorg.net'] + safe_google: false + safe_duckduckgo: true + safe_youtube: true + safe_bing: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.bind_blocklist: + block: ['Steven Black List', 'NoCoin List', 'Blocklist.site Phishing', 'AdGuard List'] + exclude: ['test.oxlorg.net', 'oxlorg.net'] + safe_google: false + safe_duckduckgo: true + safe_youtube: true + safe_bing: true + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.bind_blocklist: + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_domain.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_domain.yml new file mode 100644 index 0000000..5585821 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_domain.yml @@ -0,0 +1,238 @@ +--- + +- name: Testing BIND Domains + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'bind_domain' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 or + opn_pre1.data | length > 0 + + - name: Adding - failing because of invalid integer values + oxlorg.opnsense.bind_domain: + name: 'test1.oxlorg' + ttl: "{{ item.ttl | default(omit) }}" + refresh: "{{ item.refresh | default(omit) }}" + retry: "{{ item.retry | default(omit) }}" + expire: "{{ item.expire | default(omit) }}" + negative: "{{ item.negative | default(omit) }}" + register: opn_fail1 + failed_when: not opn_fail1.failed + loop: + - {'ttl': 40} + - {'ttl': 90000} + - {'refresh': 90000} + - {'retry': 90000} + - {'expire': 10050000} + - {'negative': 90000} + + - name: Adding - failing because invalid ips were provided + oxlorg.opnsense.bind_domain: + name: 'test1.oxlorg' + allow_notify: "{{ item.a | default(omit) }}" + primary: "{{ item.m | default(omit) }}" + register: opn_fail2 + failed_when: not opn_fail2.failed + loop: + - {'a': ['192.168.0.2000']} + - {'a': ['192.168.0.0/24']} + - {'m': ['NOT-VALID']} + + - name: Adding 1 + oxlorg.opnsense.bind_domain: + name: 'test1.oxlorg' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.bind_domain: + name: 'test1.oxlorg' + server: 'dns.oxlorg.net' + admin_mail: 'primary@oxlorg.net' + ttl: 14400 + retry: 1800 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.bind_domain: + name: 'test1.oxlorg' + server: 'dns.oxlorg.net' + admin_mail: 'primary@oxlorg.net' + ttl: 14400 + retry: 1800 + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.bind_domain: + name: 'test1.oxlorg' + server: 'dns.oxlorg.net' + admin_mail: 'primary@oxlorg.net' + ttl: 14400 + retry: 1800 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.bind_domain: + name: 'test1.oxlorg' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.bind_domain: + name: 'test2.oxlorg' + mode: 'secondary' + primary: ['192.168.0.1'] + negative: 1800 + expire: 1000000 + transfer_key_algo: 'hmac-sha512' + transfer_key_name: 'test2' + transfer_key: "{{ 'randomsecret' | b64encode }}" + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 - Nothing changed + oxlorg.opnsense.bind_domain: + name: 'test2.oxlorg' + mode: 'secondary' + primary: ['192.168.0.1'] + negative: 1800 + expire: 1000000 + transfer_key_algo: 'hmac-sha512' + transfer_key_name: 'test2' + transfer_key: "{{ 'randomsecret' | b64encode }}" + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.bind_domain: + name: 'test2.oxlorg' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.bind_domain: + name: "{{ item }}" + state: 'absent' + loop: + - 'test1.oxlorg' + - 'test2.oxlorg' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing BIND general - acl linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding dummy acl's + oxlorg.opnsense.bind_acl: + name: "{{ item.name }}" + networks: "{{ item.net }}" + loop: + - {'name': 'ANSIBLE_TEST_3_1', 'net': '192.168.0.0/25'} + - {'name': 'ANSIBLE_TEST_3_2', 'net': '192.168.0.128/25'} + when: not ansible_check_mode + + - name: Adding - setting ACLs + oxlorg.opnsense.bind_domain: + name: 'test3.oxlorg' + query_acl: 'ANSIBLE_TEST_3_1' + transfer_acl: 'ANSIBLE_TEST_3_2' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Configuring - nothing changed + oxlorg.opnsense.bind_domain: + name: 'test3.oxlorg' + query_acl: 'ANSIBLE_TEST_3_1' + transfer_acl: 'ANSIBLE_TEST_3_2' + register: opn11 + failed_when: > + opn11.failed or + opn11.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.bind_domain: + name: 'test3.oxlorg' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup ACLs + oxlorg.opnsense.bind_acl: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_3_1' + - 'ANSIBLE_TEST_3_2' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_general.yml new file mode 100644 index 0000000..c89e9e4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_general.yml @@ -0,0 +1,265 @@ +--- + +- name: Testing BIND general settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'bind_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid rate-limit + oxlorg.opnsense.bind_general: + ratelimit_count: 1010 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid cache_size + oxlorg.opnsense.bind_general: + cache_size: 100 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring - failing because of non-existent recursion-acl + oxlorg.opnsense.bind_general: + recursion_acl: 'DOES-NOT-EXIST' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Configuring - failing because of non-existent transfer-acl + oxlorg.opnsense.bind_general: + transfer_acl: 'DOES-NOT-EXIST' + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Configuring - failing because of invalid ip addresses + oxlorg.opnsense.bind_general: + listen_ipv4: "{{ item.l4 | default(omit) }}" + query_source_ipv4: "{{ item.q4 | default(omit) }}" + transfer_source_ipv4: "{{ item.t4 | default(omit) }}" + listen_ipv6: "{{ item.l6 | default(omit) }}" + query_source_ipv6: "{{ item.q6 | default(omit) }}" + transfer_source_ipv6: "{{ item.t6 | default(omit) }}" + register: opn_fail5 + failed_when: not opn_fail5.failed + loop: + - {'l4': ['127.0.0.1', '192.168.0.1000']} + - {'l4': ['127.0.0.1', 'NOT-VALID']} + - {'q4': ['NOT-VALID']} + - {'t4': ['NOT-VALID']} + - {'l6': ['NOT-VALID']} + - {'q6': ['NOT-VALID']} + - {'t6': ['NOT-VALID']} + + - name: Configuring - failing because of empty listen-address + oxlorg.opnsense.bind_general: + listen_ipv4: [] + register: opn_fail6 + failed_when: not opn_fail6.failed + + - name: Configuring - failing because of non-existent query-acl + oxlorg.opnsense.bind_general: + query_acl: 'DOES-NOT-EXIST' + register: opn_fail7 + failed_when: not opn_fail7.failed + + - name: Configuring + oxlorg.opnsense.bind_general: + listen_ipv4: ['127.0.0.1', '192.168.0.1'] + query_source_ipv4: '192.168.0.1' + transfer_source_ipv4: '192.168.0.1' + filter_aaaa_v4: true + filter_aaaa_acl: ['192.168.0.2'] + dnssec_validation: 'no' + hide_hostname: true + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.bind_general: + listen_ipv4: ['127.0.0.1', '192.168.0.1'] + query_source_ipv4: '192.168.0.1' + transfer_source_ipv4: '192.168.0.1' + filter_aaaa_v4: false + filter_aaaa_acl: ['192.168.0.2', '192.168.0.4'] + dnssec_validation: 'no' + hide_hostname: true + enabled: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.bind_general: + listen_ipv4: ['127.0.0.1', '192.168.0.1'] + query_source_ipv4: '192.168.0.1' + transfer_source_ipv4: '192.168.0.1' + filter_aaaa_v4: false + filter_aaaa_acl: ['192.168.0.2', '192.168.0.4'] + dnssec_validation: 'no' + hide_hostname: true + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.bind_general: + listen_ipv4: ['127.0.0.1', '192.168.0.1'] + query_source_ipv4: '192.168.0.1' + transfer_source_ipv4: '192.168.0.1' + filter_aaaa_v4: false + filter_aaaa_acl: ['192.168.0.2', '192.168.0.4'] + dnssec_validation: 'no' + hide_hostname: true + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.bind_general: + listen_ipv4: ['127.0.0.1', '192.168.0.1'] + query_source_ipv4: '192.168.0.1' + transfer_source_ipv4: '192.168.0.1' + filter_aaaa_v4: false + filter_aaaa_acl: ['192.168.0.2', '192.168.0.4'] + dnssec_validation: 'no' + hide_hostname: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Changing more + oxlorg.opnsense.bind_general: + listen_ipv4: ['127.0.0.1', '192.168.0.1'] + query_source_ipv4: '192.168.0.1' + transfer_source_ipv4: '192.168.0.1' + filter_aaaa_v4: false + filter_aaaa_acl: ['192.168.0.2', '192.168.0.4'] + dnssec_validation: 'no' + hide_hostname: true + hide_version: true + ratelimit: true + prefetch: false + ratelimit_count: 50 + log_size: 10 + response_policy_zones: false + ipv6: false + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.bind_general: + listen_ipv4: ['127.0.0.1', '192.168.0.1'] + query_source_ipv4: '192.168.0.1' + transfer_source_ipv4: '192.168.0.1' + filter_aaaa_v4: false + filter_aaaa_acl: ['192.168.0.2', '192.168.0.4'] + dnssec_validation: 'no' + hide_hostname: true + hide_version: true + ratelimit: true + prefetch: false + ratelimit_count: 50 + log_size: 10 + response_policy_zones: false + ipv6: false + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.bind_general: + enabled: false + ratelimit_count: + transfer_source_ipv4: '' + when: not ansible_check_mode + +- name: Testing BIND general - acl linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding dummy acl's + oxlorg.opnsense.bind_acl: + name: "{{ item.name }}" + networks: "{{ item.net }}" + loop: + - {'name': 'ANSIBLE_TEST_2_1', 'net': '192.168.0.0/25'} + - {'name': 'ANSIBLE_TEST_2_2', 'net': '192.168.0.128/25'} + when: not ansible_check_mode + + - name: Configuring - setting ACLs + oxlorg.opnsense.bind_general: + recursion_acl: 'ANSIBLE_TEST_2_1' + transfer_acl: 'ANSIBLE_TEST_2_2' + ratelimit_count: 50 + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Configuring - nothing changed + oxlorg.opnsense.bind_general: + recursion_acl: 'ANSIBLE_TEST_2_1' + transfer_acl: 'ANSIBLE_TEST_2_2' + ratelimit_count: 50 + register: opn11 + failed_when: > + opn11.failed or + opn11.changed + when: not ansible_check_mode + + - name: Cleanup general settings + oxlorg.opnsense.bind_general: + enabled: false + when: not ansible_check_mode + + - name: Cleanup ACLs + oxlorg.opnsense.bind_acl: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_record.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_record.yml new file mode 100644 index 0000000..40ad454 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_record.yml @@ -0,0 +1,252 @@ +--- + +- name: Testing BIND Records + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'bind_record' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 or + opn_pre1.data | length > 0 + + - name: Adding - failing because of non-existing domain + oxlorg.opnsense.bind_record: + domain: 'test60.oxlorg' + name: 'test1' + value: '192.168.1.1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding dummy domain + oxlorg.opnsense.bind_domain: + name: 'test4.oxlorg' + when: not ansible_check_mode + + - name: Adding - failing because of invalid values + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test1' + value: "{{ item.v }}" + type: "{{ item.t }}" + register: opn_fail1 + failed_when: not opn_fail1.failed + loop: + - {t: 'A', v: 'not-an-ip'} + - {t: 'A', v: "{'msg': '192.168.0.1', 'invalid': 'data'}"} + - {t: 'A', v: '2001:db8::1'} + - {t: 'AAAA', v: 'not-an-ip'} + - {t: 'AAAA', v: '192.168.0.1'} + + - name: Adding 1 + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test1' + value: '192.168.1.1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test1' + value: '192.168.3.1' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test1' + value: '192.168.3.1' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test1' + value: '192.168.3.1' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test1' + value: '192.168.3.1' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test2' + value: 'someRandomText' + type: 'TXT' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 - Nothing changed + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test2' + value: 'someRandomText' + type: 'TXT' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Adding 3 (round robin) + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test3' + value: '192.168.4.1' + round_robin: true + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Adding 3 - Nothing changed (round robin) + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test3' + value: '192.168.4.1' + round_robin: true + register: opn12 + failed_when: > + opn12.failed or + opn12.changed + when: not ansible_check_mode + + - name: Changing 3 (round robin) + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test3' + value: '192.168.4.2' + round_robin: true + register: opn13 + failed_when: > + opn13.failed or + not opn13.changed + when: not ansible_check_mode + + - name: Adding - failing because of unconfigured round-robin + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test3' + value: '192.168.2.1' + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn14 + failed_when: > + 'data' not in opn14 or + opn14.data | length != 4 + when: not ansible_check_mode + + - name: Removing round robin + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: 'test3' + state: 'absent' + round_robin: true + register: opn13 + failed_when: > + opn13.failed or + not opn13.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 2 + when: not ansible_check_mode + + - name: Fail to cleanup domain with active records (would leave orphaned config) + oxlorg.opnsense.bind_domain: + name: 'test4.oxlorg' + state: 'absent' + register: opn14 + failed_when: not opn14.failed + when: not ansible_check_mode + + - name: Adding 4 (mx) + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: '' + type: 'MX' + value: '10 mx.test.oxlorg.net.' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.bind_record: + domain: 'test4.oxlorg' + name: "{{ item.n }}" + type: "{{ item.t | default(omit) }}" + state: 'absent' + loop: + - {'n': 'test1'} + - {'n': 'test2', 't': 'TXT'} + - {'n': '', 't': 'MX'} + when: not ansible_check_mode + + - name: Cleanup domain + oxlorg.opnsense.bind_domain: + name: 'test4.oxlorg' + state: 'absent' + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_record_multi.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_record_multi.yml new file mode 100644 index 0000000..a990516 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/bind_record_multi.yml @@ -0,0 +1,270 @@ +--- + +- name: Testing BIND Multi-Records + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'bind_record' + + oxlorg.opnsense.bind_record_multi: + multi_control: + fail_verify: true + fail_process: true + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding dummy domain + oxlorg.opnsense.bind_domain: + name: 'test4.oxlorg' + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.bind_record_multi: + records: + - name: 'test1' + domain: 'test4.oxlorg' + value: '192.168.1.1' + type: 'A' + + - name: 'test1' + domain: 'test4.oxlorg' + type: 'TXT' + value: 'random' + + - name: 'test2' + domain: 'test4.oxlorg' + value: '192.168.2.1' + + - name: 'test3' + domain: 'test4.oxlorg' + value: '192.168.3.1' + + - name: 'test4' + domain: 'test4.oxlorg' + type: 'CNAME' + value: 'test1.test3.oxlorg' + + - name: 'test5' + domain: 'test4.oxlorg' + type: 'AAAA' + value: '2001:db8::1' + round_robin: true + + - name: 'test5' + domain: 'test4.oxlorg' + type: 'AAAA' + value: '2001:db8::2' + round_robin: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn12 + failed_when: > + 'data' not in opn12 or + opn12.data | length != 7 + when: not ansible_check_mode + + - name: Removing single + oxlorg.opnsense.bind_record_multi: + records: + - name: 'test1' + domain: 'test4.oxlorg' + value: '192.168.1.2' + type: 'A' + + - name: 'test1' + domain: 'test4.oxlorg' + type: 'TXT' + value: 'random_new' + + - name: 'test2' + domain: 'test4.oxlorg' + value: '192.168.2.1' + enabled: false + + - name: 'test3' + domain: 'test4.oxlorg' + state: 'absent' + + - name: 'test4' + domain: 'test4.oxlorg' + type: 'CNAME' + value: 'test2.test3.oxlorg' + # round-robin + + - name: 'test5' + domain: 'test4.oxlorg' + type: 'AAAA' + value: '2001:db8::1' + round_robin: true + + - name: 'test5' + domain: 'test4.oxlorg' + type: 'AAAA' + value: '2001:db8::2' + round_robin: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.bind_record_multi: + records: + - name: 'test1' + domain: 'test4.oxlorg' + value: '192.168.1.2' + type: 'A' + + - name: 'test1' + domain: 'test4.oxlorg' + type: 'TXT' + value: 'random_new' + + - name: 'test2' + domain: 'test4.oxlorg' + value: '192.168.2.1' + enabled: false + + - name: 'test3' + domain: 'test4.oxlorg' + state: 'absent' + + - name: 'test4' + domain: 'test4.oxlorg' + type: 'CNAME' + value: 'test2.test3.oxlorg' + # round-robin + + - name: 'test5' + domain: 'test4.oxlorg' + type: 'AAAA' + value: '2001:db8::1' + round_robin: true + + - name: 'test5' + domain: 'test4.oxlorg' + type: 'AAAA' + value: '2001:db8::2' + round_robin: true + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 6 + when: not ansible_check_mode + + - name: Removing - round robin + oxlorg.opnsense.bind_record_multi: + records: + - name: 'test5' + domain: 'test4.oxlorg' + type: 'AAAA' + round_robin: true + + - name: 'test5' + domain: 'test4.oxlorg' + type: 'AAAA' + round_robin: true + + multi_control: + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 4 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.bind_record_multi: + records: + - name: 'test1' + domain: 'test4.oxlorg' + type: 'A' + + - name: 'test1' + domain: 'test4.oxlorg' + type: 'TXT' + + - name: 'test2' + domain: 'test4.oxlorg' + + - name: 'test3' + domain: 'test4.oxlorg' + + - name: 'test4' + domain: 'test4.oxlorg' + type: 'CNAME' + + multi_control: + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.bind_record_multi: + records: + - name: 'test1' + domain: 'test4.oxlorg' + value: '192.168.1.1' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Purging all + oxlorg.opnsense.bind_record_multi: + multi_control: + purge_all: true + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Cleanup domain + oxlorg.opnsense.bind_domain: + name: 'test4.oxlorg' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/cron.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/cron.yml new file mode 100644 index 0000000..4419f9a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/cron.yml @@ -0,0 +1,139 @@ +--- + +- name: Testing Cron + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'cron' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + # NOTE: one exists because of IDS + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 1 + + - name: Removing - does not exist + oxlorg.opnsense.cron: + description: 'ANSIBLE_TEST_1' + state: 'absent' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + + - name: Adding daily firmware update check + oxlorg.opnsense.cron: + description: 'ANSIBLE_TEST_1' + command: 'firmware poll' + minutes: '0' + hours: '0' + days: '*' + + - name: Adding 2 + oxlorg.opnsense.cron: + description: 'ANSIBLE_TEST_2' + command: 'system remote backup' + minutes: '26' + hours: '15' + days: '5,9,26' + months: '8' + weekdays: '4,5,7' + who: 'root' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.cron: + description: 'ANSIBLE_TEST_2' + command: 'system remote backup' + minutes: '26' + hours: '15' + days: '5,9,26' + months: '8' + weekdays: '4,5,7' + who: 'root' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + when: not ansible_check_mode + + - name: Disabling 2 + oxlorg.opnsense.cron: + description: 'ANSIBLE_TEST_2' + command: 'system remote backup' + minutes: '26' + hours: '15' + days: '5,9,26' + months: '8' + weekdays: '4,5,7' + who: 'root' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 2 - nothing changed + oxlorg.opnsense.cron: + description: 'ANSIBLE_TEST_2' + command: 'system remote backup' + minutes: '26' + hours: '15' + days: '5,9,26' + months: '8' + weekdays: '4,5,7' + who: 'root' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 2 + oxlorg.opnsense.cron: + description: 'ANSIBLE_TEST_2' + command: 'system remote backup' + minutes: '26' + hours: '15' + days: '5,9,26' + months: '8' + weekdays: '4,5,7' + who: 'root' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 3 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.cron: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_controlagent.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_controlagent.yml new file mode 100644 index 0000000..de7547c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_controlagent.yml @@ -0,0 +1,72 @@ +--- + +- name: Testing DHCP-Controlagent + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Configuring + oxlorg.opnsense.dhcp_controlagent: + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.dhcp_controlagent: + enabled: true + http_port: 8082 + http_host: '192.168.0.55' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Disabling 1 + oxlorg.opnsense.dhcp_controlagent: + enabled: false + http_port: 8082 + http_host: '192.168.0.55' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.dhcp_controlagent: + enabled: false + http_port: 8082 + http_host: '192.168.0.55' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.dhcp_controlagent: + enabled: true + http_port: 8082 + http_host: '192.168.0.55' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dhcp_controlagent: + enabled: false + http_host: '127.0.0.1' + http_port: 8000 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_general.yml new file mode 100644 index 0000000..7916439 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_general.yml @@ -0,0 +1,67 @@ +--- + +- name: Testing DHCP Setting + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dhcp_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid lifetime + oxlorg.opnsense.dhcp_general: + lifetime: -1 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring + oxlorg.opnsense.dhcp_general: + enabled: true + interfaces: ['opt1'] + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.dhcp_general: + enabled: true + interfaces: ['opt1', 'lan'] + fw_rules: false + lifetime: 5000 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.dhcp_general: + enabled: true + interfaces: ['opt1', 'lan'] + fw_rules: false + lifetime: 5000 + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dhcp_general: + enabled: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_reservation.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_reservation.yml new file mode 100644 index 0000000..c283b50 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_reservation.yml @@ -0,0 +1,160 @@ +--- + +- name: DHCP Reservation + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dhcp_reservation' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.69.78' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of non-existent subnet + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.59.0/24' + ip: '192.168.59.76' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of IP-address + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.69.7x' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of missing mac-address + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.69.7x' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 - failing because of ip outside subnet (server-side) + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.70.7' + register: opn_fail4 + failed_when: not opn_fail4.failed + when: not ansible_check_mode + + - name: Adding dummy DHCP-subnet + oxlorg.opnsense.dhcp_subnet: + subnet: '192.168.69.0/24' + check_mode: false + + - name: Adding 1 + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.69.76' + mac: 'aa:aa:aa:aa:aa:aa' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - failing because of duplicate mac-address (server-side) + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.69.77' + mac: 'aa:aa:aa:aa:aa:aa' + register: opn_fail4 + failed_when: not opn_fail4.failed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.69.86' + mac: 'aa:aa:aa:aa:aa:bb' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing 2 + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.69.86' + mac: 'aa:aa:aa:aa:aa:cc' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Changing 2 - Nothing changed + oxlorg.opnsense.dhcp_reservation: + subnet: '192.168.69.0/24' + ip: '192.168.69.86' + mac: 'aa:aa:aa:aa:aa:cc' + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 2 + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.dhcp_reservation: + ip: '192.168.69.86' + state: absent + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dhcp_reservation: + ip: "{{ item }}" + state: 'absent' + loop: + - '192.168.69.76' + - '192.168.69.86' + when: not ansible_check_mode + + - name: Cleanup dummy DHCP-subnet + oxlorg.opnsense.dhcp_subnet: + subnet: '192.168.69.0/24' + state: 'absent' + check_mode: false + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_subnet.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_subnet.yml new file mode 100644 index 0000000..ca07273 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcp_subnet.yml @@ -0,0 +1,136 @@ +--- + +- name: DHCP Subnet + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dhcp_subnet' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dhcp_subnet: + subnet: '192.168.88.0/24' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + # - name: Adding 1 - failing because of non-existent subnet + # oxlorg.opnsense.dhcp_subnet: + # subnet: '192.168.59.0/24' + # ip: '192.168.59.76' + # register: opn_fail1 + # failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.dhcp_subnet: + subnet: '192.168.88.0/24' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 + oxlorg.opnsense.dhcp_subnet: + subnet: '192.168.89.0/24' + pools: + - '192.168.89.10-192.168.89.20' + - '192.168.89.40-192.168.89.50' + auto_options: false + gateway: '192.168.89.200' + dns: '1.1.1.1' + domain: 'test.lan' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing 2 + oxlorg.opnsense.dhcp_subnet: + subnet: '192.168.89.0/24' + pools: + - '192.168.89.40-192.168.89.60' + - '192.168.89.110-192.168.89.120' + auto_options: false + gateway: '192.168.89.240' + dns: ['1.1.1.2', '1.0.0.2'] + domain: 'test.lan' + time_servers: '192.168.89.240' + tftp_server: '192.168.89.210' + tftp_file: 'openwrt.bin' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Changing 2 - Nothing changed + oxlorg.opnsense.dhcp_subnet: + subnet: '192.168.89.0/24' + pools: + - '192.168.89.40-192.168.89.60' + - '192.168.89.110-192.168.89.120' + auto_options: false + gateway: '192.168.89.240' + dns: ['1.1.1.2', '1.0.0.2'] + domain: 'test.lan' + time_servers: '192.168.89.240' + tftp_server: '192.168.89.210' + tftp_file: 'openwrt.bin' + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 2 + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.dhcp_subnet: + subnet: '192.168.89.0/24' + state: absent + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dhcp_subnet: + subnet: "{{ item }}" + state: 'absent' + loop: + - '192.168.88.0/24' + - '192.168.89.0/24' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcrelay_destination.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcrelay_destination.yml new file mode 100644 index 0000000..83334c4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcrelay_destination.yml @@ -0,0 +1,113 @@ +--- + +# todo: test default matching + +- name: Testing DHCRelay Destination + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dhcrelay_destination' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn1 + failed_when: > + 'data' not in opn1 or + opn1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + reload: false + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + + - name: Adding - failing because of invalid values + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_1' + server: '{{ item }}' + reload: false + register: opn_fail1 + failed_when: not opn_fail1.failed + loop: + - ['dhcp'] + - ['172.0.0'] + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_1' + server: + - '192.168.254.254' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 2 + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_2' + server: + - '192.168.254.252' + - '192.168.254.253' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_2' + server: + - '192.168.254.252' + - '192.168.254.253' + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcrelay_relay.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcrelay_relay.yml new file mode 100644 index 0000000..93d6bb3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dhcrelay_relay.yml @@ -0,0 +1,134 @@ +--- +- name: Testing DHCRelay Relay + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dhcrelay_relay' + + vars: + if_dhcrelay: "{{ lookup('ansible.builtin.env', 'TEST_DHCRELAY_IF') | default('lan', true) }}" + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn1 + failed_when: > + 'data' not in opn1 or + opn1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dhcrelay_relay: + interface: 'DOESNOTEXIST' + state: 'absent' + reload: false + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + + - name: Adding - failing because of invalid interface + oxlorg.opnsense.dhcrelay_relay: + interface: 'DOESNOTEXIST' + server: 'ANSIBLE_TEST_1_1' + reload: false + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding - failing because of invalid destination + oxlorg.opnsense.dhcrelay_relay: + interface: '{{ if_dhcrelay }}' + destination: 'DOESNOTEXIST' + reload: false + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Adding destination + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_1' + server: + - '192.168.254.254' + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.dhcrelay_relay: + interface: '{{ if_dhcrelay }}' + destination: 'ANSIBLE_TEST_1_1' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.dhcrelay_relay: + interface: '{{ if_dhcrelay }}' + destination: 'ANSIBLE_TEST_1_1' + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.dhcrelay_relay: + interface: '{{ if_dhcrelay }}' + destination: 'ANSIBLE_TEST_1_1' + enabled: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 - nothing changed + oxlorg.opnsense.dhcrelay_relay: + interface: '{{ if_dhcrelay }}' + destination: 'ANSIBLE_TEST_1_1' + enabled: true + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn5 + failed_when: > + 'data' not in opn5 or + opn5.data | length != 1 + when: not ansible_check_mode + + - name: Removing + oxlorg.opnsense.dhcrelay_relay: + interface: '{{ if_dhcrelay }}' + state: 'absent' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Cleanup destination + oxlorg.opnsense.dhcrelay_destination: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_boot.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_boot.yml new file mode 100644 index 0000000..06e621d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_boot.yml @@ -0,0 +1,120 @@ +--- + +- name: Testing dnsmasq boot + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + # match_fields: ['description'] + + oxlorg.opnsense.list: + target: 'dnsmasq_boot' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dnsmasq_boot: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing filename + oxlorg.opnsense.dnsmasq_boot: + description: 'ANSIBLE_TEST_1_1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.dnsmasq_boot: + description: 'ANSIBLE_TEST_1_1' + filename: '/tftpboot/pxelinux.0' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.dnsmasq_boot: + description: 'ANSIBLE_TEST_1_1' + filename: '/tftpboot/pxelinux.0' + servername: 'tftp.example.com' + address: '192.168.1.1' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 + oxlorg.opnsense.dnsmasq_boot: + description: 'ANSIBLE_TEST_1_2' + filename: '/tftpboot/pxelinux.0' + servername: 'tftp.example.com' + address: '192.168.1.2' + interface: TEST + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.dnsmasq_boot: + description: 'ANSIBLE_TEST_1_2' + filename: '/tftpboot/pxelinux.0' + servername: 'tftp.example.com' + address: '192.168.1.2' + interface: TEST + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.dnsmasq_boot: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn6 + failed_when: > + 'data' not in opn6 or + opn6.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dnsmasq_boot: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_domain.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_domain.yml new file mode 100644 index 0000000..dbee6d6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_domain.yml @@ -0,0 +1,120 @@ +--- + +- name: Testing stuff + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dnsmasq_domain' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dnsmasq_domain: + domain: 'test1.oxlorg' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + +# - name: Adding 1 - failing because of invalid value +# oxlorg.opnsense.dnsmasq_domain: +# field1: 'INVALID-IP' +# register: opn_fail1 +# failed_when: not opn_fail1.failed +# +# - name: Adding 1 - failing because of invalid domain +# oxlorg.opnsense.dnsmasq_domain: +# field1: '!INVALID-DOMAIN!' +# field2: 'INVALID-IP' +# register: opn_fail2 +# failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.dnsmasq_domain: + domain: 'test1.oxlorg' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.dnsmasq_domain: + domain: 'test1.oxlorg' + ip: 192.168.100.100 + port: 5353 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 + oxlorg.opnsense.dnsmasq_domain: + domain: 'test2.oxlorg' + ip: 192.168.100.101 + port: 5353 + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.dnsmasq_domain: + domain: 'test2.oxlorg' + ip: 192.168.100.101 + port: 5353 + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.dnsmasq_domain: + domain: 'test2.oxlorg' + state: 'absent' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn6 + failed_when: > + 'data' not in opn6 or + opn6.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dnsmasq_domain: + domain: '{{ item }}' + state: 'absent' + loop: + - 'test1.oxlorg' + - 'test2.oxlorg' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_general.yml new file mode 100644 index 0000000..c211051 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_general.yml @@ -0,0 +1,125 @@ +--- + +- name: Testing dnsmasq general + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + tasks: + - name: Listing + oxlorg.opnsense.list: + target: 'dnsmasq_general' + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length == 0 + + - name: Enable and configure all dnsmasq options + oxlorg.opnsense.dnsmasq_general: + enabled: true + interfaces: ['opt1', 'opt2'] + strictbind: true + dns_port: 2053 + dnssec: true + resolve_etc_hosts: false + log_queries: true + dns_forward_max: 4242 + cache_size: 12345 + local_ttl: 23 + ident: true + strict_order: true + domain_needed: true + resolv_system: false + forward_private_reverse: false + add_mac: 'text' + add_subnet: true + strip_subnet: true + dhcp_disable_interfaces: 'opt2' + dhcp_fqdn: true + dhcp_domain: 'dhcp.example' + dhcp_local: false + dhcp_lease_max: 1000 + dhcp_authoritative: true + dhcp_reply_delay: 10 + dhcp_default_fw_rules: false + dhcp_enable_ra: true + dhcp_hasync: false + regdhcp: true + regdhcpdomain: 'test.domain' + regdhcpstatic: true + dhcpfirst: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Nothing changed + oxlorg.opnsense.dnsmasq_general: + enabled: true + interfaces: ['TEST', 'TEST2'] + strictbind: true + dns_port: 2053 + dnssec: true + resolve_etc_hosts: false + log_queries: true + dns_forward_max: 4242 + cache_size: 12345 + local_ttl: 23 + ident: true + strict_order: true + domain_needed: true + resolv_system: false + forward_private_reverse: false + add_mac: 'text' + add_subnet: true + strip_subnet: true + dhcp_disable_interfaces: 'TEST2' + dhcp_fqdn: true + dhcp_domain: 'dhcp.example' + dhcp_local: false + dhcp_lease_max: 1000 + dhcp_authoritative: true + dhcp_reply_delay: 10 + dhcp_default_fw_rules: false + dhcp_enable_ra: true + dhcp_hasync: false + regdhcp: true + regdhcpdomain: 'test.domain' + regdhcpstatic: true + dhcpfirst: true + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Changing + oxlorg.opnsense.dnsmasq_general: + enabled: true + interfaces: ['opt1'] + strictbind: true + port: 2054 + dnssec: true + resolve_etc_hosts: false + log_queries: false + ident: true + strict_order: true + domain_needed: false + forward_private_reverse: false + regdhcp: true + regdhcpdomain: 'test2.domain' + regdhcpstatic: false + dhcpfirst: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dnsmasq_general: + enabled: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_host.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_host.yml new file mode 100644 index 0000000..6985ec4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_host.yml @@ -0,0 +1,129 @@ +--- + +- name: Testing dnsmasq host + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dnsmasq_host' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dnsmasq_host: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid domain + oxlorg.opnsense.dnsmasq_host: + description: 'ANSIBLE_TEST_1_2' + host: server + domain: example:com + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.dnsmasq_host: + description: 'ANSIBLE_TEST_1_1' + ip: '192.168.1.99' + hardware_addr: '00:11:22:33:44:55' + comments: 'DHCP Client' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.dnsmasq_host: + description: 'ANSIBLE_TEST_1_1' + ip: '192.168.1.99' + hardware_addr: '00:11:22:33:44:55' + comments: 'DHCP Client' + lease_time: 3600 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 + oxlorg.opnsense.dnsmasq_host: + description: 'ANSIBLE_TEST_1_2' + host: server + domain: example.com + cnames: + - server2.excample.com + ip: '192.168.1.10' + comments: 'DNS Entry' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.dnsmasq_host: + description: 'ANSIBLE_TEST_1_2' + host: server + domain: example.com + cnames: + - server2.excample.com + ip: '192.168.1.10' + comments: 'DNS Entry' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.dnsmasq_host: + description: 'ANSIBLE_TEST_1_2' + host: server + state: 'absent' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn6 + failed_when: > + 'data' not in opn6 or + opn6.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dnsmasq_host: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_option.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_option.yml new file mode 100644 index 0000000..cb9a7a1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_option.yml @@ -0,0 +1,153 @@ +--- + +- name: Testing stuff + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dnsmasq_option' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of no option + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 2 - failing because of invalid option interface + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_1' + type: match + set_tag: Foo + interface: TEST + option: 211 # pxelinux reboottime + value: 30 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 2 - failing because of missin set_tag + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_1' + type: match + option: 211 # pxelinux reboottime + value: 30 + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_1' + option: 211 # pxelinux reboottime + value: 30 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_1' + option: 211 # pxelinux reboottime + value: 30 + interface: TEST + force: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Adding mock tag + oxlorg.opnsense.dnsmasq_tag: + tag: 'AnsibleTest1' + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_2' + type: match + option: 211 # pxelinux reboottime + value: 30 + set_tag: 'AnsibleTest1' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 - nothing changed + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_2' + type: match + option: 211 # pxelinux reboottime + value: 30 + set_tag: 'AnsibleTest1' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.dnsmasq_option: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dnsmasq_option: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Cleanup mock tag + oxlorg.opnsense.dnsmasq_tag: + tag: 'AnsibleTest1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_range.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_range.yml new file mode 100644 index 0000000..709a338 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_range.yml @@ -0,0 +1,131 @@ +--- + +- name: Testing dnsmasq range + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dnsmasq_range' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dnsmasq_range: + description: ANSIBLE_TEST_1_1 + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 + oxlorg.opnsense.dnsmasq_range: + description: 'ANSIBLE_TEST_1_1' + interface: 'TEST' + start_addr: '192.168.1.100' + end_addr: '192.168.1.150' + subnet_mask: '255.255.255.0' + domain_type: 'interface' + domain: 'opn.dnsmasq' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.dnsmasq_range: + description: 'ANSIBLE_TEST_1_1' + interface: 'TEST' + start_addr: '192.168.1.100' + end_addr: '192.168.1.200' + subnet_mask: '255.255.255.0' + domain_type: 'interface' + domain: 'dnsmasq.opn' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 + oxlorg.opnsense.dnsmasq_range: + description: 'ANSIBLE_TEST_1_2' + interface: 'TEST' + start_addr: '::1' + end_addr: '::200' + constructor: 'TEST' + domain_type: 'interface' + domain: 'dnsmasq.opn' + ra_mode: + - 'ra-stateless' + - 'ra-names' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.dnsmasq_range: + description: 'ANSIBLE_TEST_1_2' + interface: 'TEST' + start_addr: '::1' + end_addr: '::200' + constructor: 'TEST' + domain_type: 'interface' + domain: 'dnsmasq.opn' + ra_mode: + - 'ra-stateless' + - 'ra-names' + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.dnsmasq_range: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn6 + failed_when: > + 'data' not in opn6 or + opn6.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dnsmasq_range: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_tag.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_tag.yml new file mode 100644 index 0000000..6ae3cab --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/dnsmasq_tag.yml @@ -0,0 +1,78 @@ +--- + +- name: Testing dnsmasq tags + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'dnsmasq_tag' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.dnsmasq_tag: + name: 'AnsileTest1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid value + oxlorg.opnsense.dnsmasq_tag: + name: 'ANSIBLE_TEST_1_1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.dnsmasq_tag: + name: 'AnsileTest1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.dnsmasq_tag: + name: 'AnsileTest1' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.dnsmasq_tag: + name: 'AnsileTest1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bfd_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bfd_general.yml new file mode 100644 index 0000000..3344ee5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bfd_general.yml @@ -0,0 +1,31 @@ +--- + +- name: Testing FRR-BFD General + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.frr_bfd_general: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Enabling BFD + oxlorg.opnsense.frr_bfd_general: + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Disabling BFD + oxlorg.opnsense.frr_bfd_general: + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bfd_neighbor.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bfd_neighbor.yml new file mode 100644 index 0000000..9684be3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bfd_neighbor.yml @@ -0,0 +1,140 @@ +--- + +- name: Testing FRR-BFD Neighbors + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bfd_neighbor' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid value + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.1000' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.1' + description: 'changed' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.1' + description: 'changed' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.1' + description: 'changed' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.1' + description: 'changed' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.0/28' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.0/28' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_bfd_neighbor: + ip: '10.0.0.0/28' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bfd_neighbor: + ip: "{{ item }}" + state: 'absent' + loop: + - '10.0.0.1' + - '10.0.0.0/28' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_as_path.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_as_path.yml new file mode 100644 index 0000000..fb271e8 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_as_path.yml @@ -0,0 +1,160 @@ +--- + +- name: Testing BGP AS Paths + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_as_path' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid number + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_1' + number: 200 + action: 'permit' + as_pattern: 'as-pattern' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_1' + number: 20 + action: 'permit' + as_pattern: 'as-pattern' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_1' + number: 25 + action: 'permit' + as_pattern: 'as-pattern-new' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_1' + number: 25 + action: 'permit' + as_pattern: 'as-pattern-new' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_1' + number: 25 + action: 'permit' + as_pattern: 'as-pattern-new' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_1' + number: 25 + action: 'permit' + as_pattern: 'as-pattern-new' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_2' + number: 80 + action: 'deny' + as_pattern: 'what' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_2' + number: 80 + action: 'deny' + as_pattern: 'what' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bgp_as_path: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_community_list.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_community_list.yml new file mode 100644 index 0000000..02a74d4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_community_list.yml @@ -0,0 +1,178 @@ +--- + +- name: Testing BGP community lists + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_community_list' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid sequence-number + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_1' + number: 20 + seq: 1000 + action: 'permit' + community: 'test' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid number + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_1' + number: 1000 + seq: 20 + action: 'permit' + community: 'test' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_1' + number: 20 + seq: 20 + action: 'permit' + community: 'test' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_1' + number: 25 + seq: 25 + action: 'deny' + community: 'test' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_1' + number: 25 + seq: 25 + action: 'deny' + community: 'test' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_1' + number: 25 + seq: 25 + action: 'deny' + community: 'test' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_1' + number: 25 + seq: 25 + action: 'deny' + community: 'test' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_2' + number: 40 + seq: 30 + action: 'deny' + community: 'test2' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_2' + number: 40 + seq: 30 + action: 'deny' + community: 'test2' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bgp_community_list: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_general.yml new file mode 100644 index 0000000..15f696e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_general.yml @@ -0,0 +1,102 @@ +--- + +- name: Testing FRR-BGP general settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid AS NR + oxlorg.opnsense.frr_bgp_general: + as_number: 1000000000000000000000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring + oxlorg.opnsense.frr_bgp_general: + as_number: 1337 + id: '10.0.0.1' + graceful: true + networks: ['10.0.10.0/24'] + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.frr_bgp_general: + as_number: 1337 + id: '10.0.0.1' + graceful: false + networks: ['10.0.10.0/25'] + distance: 21 + enabled: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_bgp_general: + as_number: 1337 + id: '10.0.0.1' + graceful: false + networks: ['10.0.10.0/25'] + enabled: false + distance: 21 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bgp_general: + as_number: 1337 + id: '10.0.0.1' + graceful: false + networks: ['10.0.10.0/25'] + enabled: false + distance: 21 + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bgp_general: + as_number: 1337 + id: '10.0.0.1' + graceful: false + networks: ['10.0.10.0/25'] + distance: 21 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bgp_general: + as_number: 1337 + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_neighbor.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_neighbor.yml new file mode 100644 index 0000000..b7d9cf1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_neighbor.yml @@ -0,0 +1,327 @@ +--- + +- name: Testing FRR BGP Neighbors + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.frr_bgp_neighbor: + match_fields: ['description'] + + oxlorg.opnsense.list: + target: 'frr_bgp_neighbor' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid keepalive + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + keepalive: 3000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid ip-address + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + ip: 'INVALID-IP' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of invalid local ip-address + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + local_ip: 'INVALID-IP' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 - failing because of non-existent prefix-list + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + prefix_list_in: 'DOES-NOT-EXIST' + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding 1 - failing because of non-existent route-map + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + route_map_out: 'DOES-NOT-EXIST' + register: opn_fail5 + failed_when: not opn_fail5.failed + + - name: Adding 1 + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + ip: '10.0.0.1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + ip: '10.0.0.1' + password: "{{ 'random' | hash('md5') }}" + weight: 200 + local_ip: '10.0.0.254' + source_int: 'opt1' + multi_protocol: true + keepalive: 45 + hold_down: 135 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + ip: '10.0.0.1' + password: "{{ 'random' | hash('md5') }}" + weight: 200 + local_ip: '10.0.0.254' + source_int: 'opt1' + multi_protocol: true + keepalive: 45 + hold_down: 135 + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + ip: '10.0.0.1' + password: "{{ 'random' | hash('md5') }}" + weight: 200 + local_ip: '10.0.0.254' + source_int: 'opt1' + multi_protocol: true + keepalive: 45 + hold_down: 135 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_1' + as_number: 1337 + ip: '10.0.0.1' + password: "{{ 'random' | hash('md5') }}" + weight: 200 + local_ip: '10.0.0.254' + source_int: 'opt1' + multi_protocol: true + keepalive: 45 + hold_down: 135 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_2' + as_number: 1338 + ip: '10.0.1.1' + password: "{{ 'oh-so-random' | hash('md5') }}" + local_ip: '10.0.1.254' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_2' + as_number: 1338 + ip: '10.0.1.1' + password: "{{ 'oh-so-random' | hash('md5') }}" + local_ip: '10.0.1.254' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bgp_neighbor: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR BGP Neighbors - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.frr_bgp_neighbor: + match_fields: ['description'] + + oxlorg.opnsense.list: + target: 'frr_bgp_neighbor' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre3 + failed_when: > + 'data' not in opn_pre3 or + opn_pre3.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy prefix-list + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_2_1' + network: '10.0.10.0/24' + seq: 51 + action: 'permit' + when: not ansible_check_mode + + - name: Adding dummy prefix-list 2 (same name, other sequence) + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_2_1' + network: '10.0.11.0/24' + seq: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding neighbor linked to prefix-list + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_2_1' + prefix_list_in: 'ANSIBLE_TEST_2_1' + as_number: 1337 + ip: '10.0.0.1' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Adding dummy route-map + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_3_1' + id: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding neighbor linked to route-map + oxlorg.opnsense.frr_bgp_neighbor: + description: 'ANSIBLE_TEST_2_2' + route_map_out: 'ANSIBLE_TEST_3_1' + as_number: 1338 + ip: '10.0.1.1' + register: opn13 + failed_when: > + opn13.failed or + not opn13.changed + when: not ansible_check_mode + + - name: Cleanup neighbors + oxlorg.opnsense.frr_bgp_neighbor: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + when: not ansible_check_mode + + - name: Cleanup prefix-list + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_2_1' + seq: "{{ item }}" + state: 'absent' + loop: + - 50 + - 51 + when: not ansible_check_mode + + - name: Cleanup route-map + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_3_1' + id: 50 + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_peer_group.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_peer_group.yml new file mode 100644 index 0000000..18fce83 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_peer_group.yml @@ -0,0 +1,277 @@ +--- + +- name: Testing FRR BGP Peer Groups + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_peer_group' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid as_mode + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + as_mode: 'DOES-NOT-EXIST' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of non-existent prefix-list + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + as_number: 1337 + prefix_list_in: 'DOES-NOT-EXIST' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of non-existent route-map + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + as_number: 1337 + route_map_out: 'DOES-NOT-EXIST' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + as_number: 1337 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + as_number: 1338 + source_int: 'opt1' + next_hop_self: true + send_default_route: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + as_number: 1338 + source_int: 'opt1' + next_hop_self: true + send_default_route: true + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + as_number: 1338 + source_int: 'opt1' + next_hop_self: true + send_default_route: true + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_1' + as_number: 1338 + source_int: 'opt1' + next_hop_self: true + send_default_route: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_2' + as_mode: internal + listen_ranges: ['192.168.0.0/24'] + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_2' + as_mode: internal + listen_ranges: ['192.168.0.0/24'] + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bgp_peer_group: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR BGP Peer Groups - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_peer_group' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre3 + failed_when: > + 'data' not in opn_pre3 or + opn_pre3.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy prefix-list + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_2_1' + network: '10.0.10.0/24' + seq: 51 + action: 'permit' + when: not ansible_check_mode + + - name: Adding dummy prefix-list 2 (same name, other sequence) + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_2_1' + network: '10.0.11.0/24' + seq: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding neighbor linked to prefix-list + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_2_1' + prefix_list_in: 'ANSIBLE_TEST_2_1' + as_number: 1337 + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Adding dummy route-map + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_3_1' + id: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding neighbor linked to route-map + oxlorg.opnsense.frr_bgp_peer_group: + name: 'ANSIBLE_TEST_2_2' + route_map_out: 'ANSIBLE_TEST_3_1' + as_number: 1338 + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Cleanup peergroups + oxlorg.opnsense.frr_bgp_peer_group: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + when: not ansible_check_mode + + - name: Cleanup prefix-list + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_2_1' + seq: "{{ item }}" + state: 'absent' + loop: + - 50 + - 51 + when: not ansible_check_mode + + - name: Cleanup route-map + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_3_1' + id: 50 + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_prefix_list.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_prefix_list.yml new file mode 100644 index 0000000..9a8b4f0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_prefix_list.yml @@ -0,0 +1,174 @@ +--- + +- name: Testing BGP prefix lists + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_prefix_list' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 55 + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid ip-version + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_1' + version: 'IPv5' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_1' + network: '10.0.10.0/24' + seq: 55 + action: 'permit' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_1' + network: '10.0.11.0/24' + seq: 55 + action: 'permit' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_1' + network: '10.0.11.0/24' + seq: 55 + action: 'permit' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_1' + network: '10.0.11.0/24' + seq: 55 + action: 'permit' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_1' + network: '10.0.11.0/24' + seq: 55 + action: 'permit' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_2' + network: '10.0.20.0/24' + seq: 56 + action: 'deny' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_2' + network: '10.0.20.0/24' + seq: 56 + action: 'deny' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Adding 3 (same name, different sequence) + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_2' + network: '10.0.30.0/24' + seq: 57 + action: 'deny' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_1_2' + seq: 56 + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bgp_prefix_list: + name: "{{ item.n }}" + seq: "{{ item.s | default(omit) }}" + state: 'absent' + loop: + - {'n': 'ANSIBLE_TEST_1_1', 's': 55} + - {'n': 'ANSIBLE_TEST_1_2', 's': 56} + - {'n': 'ANSIBLE_TEST_1_2', 's': 57} + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_redistribution.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_redistribution.yml new file mode 100644 index 0000000..07e5084 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_redistribution.yml @@ -0,0 +1,219 @@ +--- + +- name: Testing FRR BGP Redistribution + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_redistribution' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid redistribute + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: ansible + register: opn_fail1 + failed_when: not opn_fail1.failed + + + - name: Adding 1 - failing because of non-existent route-map + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: ospf + route_map: 'DOES-NOT-EXIST' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: ospf + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_2' + redistribute: ospf + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_2' + redistribute: ospf + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bgp_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR BGP Redistribution - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_redistribution' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre2 + failed_when: > + 'data' not in opn_pre2 or + opn_pre2.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy route-map + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_3_1' + id: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding redistribution linked to route-map + oxlorg.opnsense.frr_bgp_redistribution: + description: 'ANSIBLE_TEST_2_1' + redistribute: ospf + route_map: 'ANSIBLE_TEST_3_1' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Cleanup redistributions + oxlorg.opnsense.frr_bgp_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + when: not ansible_check_mode + + - name: Cleanup route-map + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_3_1' + id: 50 + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_route_map.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_route_map.yml new file mode 100644 index 0000000..582401d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_bgp_route_map.yml @@ -0,0 +1,364 @@ +--- + +- name: Testing FRR BGP Route-Maps + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_route_map' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid id + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 700 + action: 'permit' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of non-existent prefix-list + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 50 + action: 'permit' + prefix_list: 'DOES-NOT-EXIST' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of non-existent as-path + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 50 + action: 'permit' + as_path_list: 'DOES-NOT-EXIST' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 - failing because of non-existent community + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 50 + action: 'permit' + community_list: 'DOES-NOT-EXIST' + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding 1 + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 50 + action: 'permit' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + match_fields: ['name'] + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + match_fields: ['name'] + register: opn11 + failed_when: > + opn11.failed or + opn11.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 - same name but other id + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 51 + action: 'permit' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 51 + action: 'permit' + register: opn10 + failed_when: > + opn10.failed or + opn10.changed + when: not ansible_check_mode + + - name: Adding 3 + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_2' + id: 65 + action: 'permit' + set: 'local-preference 300' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 3 - nothing changed + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_2' + id: 65 + action: 'permit' + set: 'local-preference 300' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 3 + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_1_2' + id: 65 + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_bgp_route_map: + name: "{{ item.name }}" + id: "{{ item.id }}" + state: 'absent' + with_items: + - {'name': 'ANSIBLE_TEST_1_1', id: 45} + - {'name': 'ANSIBLE_TEST_1_1', id: 51} + - {'name': 'ANSIBLE_TEST_1_2', id: 65} + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR BGP Route-Maps - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_bgp_route_map' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre3 + failed_when: > + 'data' not in opn_pre3 or + opn_pre3.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy prefix-list + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_3_1' + network: '10.0.10.0/24' + seq: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding route-map linked to prefix-list 1 + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_2_1' + prefix_list: {'ANSIBLE_TEST_3_1': 50} + id: 65 + action: 'permit' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Adding dummy prefix-list 2 (same name, different sequence) + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_3_1' + network: '10.0.10.0/24' + seq: 51 + action: 'permit' + when: not ansible_check_mode + + - name: Adding route-map linked to prefix-lists 1+2 + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_2_1' + prefix_list: {'ANSIBLE_TEST_3_1': [50, 51]} + id: 65 + action: 'permit' + register: opn17 + failed_when: > + opn17.failed or + not opn17.changed + when: not ansible_check_mode + + - name: Adding dummy community-list + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_2_1' + number: 20 + seq: 20 + action: 'permit' + community: 'test' + when: not ansible_check_mode + + - name: Adding route-map linked to community-list + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_2_2' + community_list: ['ANSIBLE_TEST_2_1'] + id: 66 + action: 'permit' + register: opn13 + failed_when: > + opn13.failed or + not opn13.changed + when: not ansible_check_mode + + - name: Adding dummy as-path + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_2_1' + number: 20 + action: 'permit' + as_pattern: 'as-pattern' + when: not ansible_check_mode + + - name: Adding route-map linked to as-path + oxlorg.opnsense.frr_bgp_route_map: + name: 'ANSIBLE_TEST_2_3' + as_path_list: ['ANSIBLE_TEST_2_1'] + id: 67 + action: 'permit' + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + when: not ansible_check_mode + + - name: Cleanup route-maps + oxlorg.opnsense.frr_bgp_route_map: + name: "{{ item }}" + state: 'absent' + match_fields: ['name'] + loop: + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + - 'ANSIBLE_TEST_2_3' + when: not ansible_check_mode + + - name: Cleanup prefix-list + oxlorg.opnsense.frr_bgp_prefix_list: + name: 'ANSIBLE_TEST_3_1' + seq: "{{ item }}" + state: 'absent' + loop: + - 50 + - 51 + when: not ansible_check_mode + + - name: Cleanup community-list + oxlorg.opnsense.frr_bgp_community_list: + description: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup as-path + oxlorg.opnsense.frr_bgp_as_path: + description: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_diagnostic.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_diagnostic.yml new file mode 100644 index 0000000..a872240 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_diagnostic.yml @@ -0,0 +1,30 @@ +--- + +- name: Testing FRR info querying + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.frr_diagnostic: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Showing BGP neighbors + oxlorg.opnsense.frr_diagnostic: + target: 'bgpneighbors' + register: opn1 + failed_when: > + opn1.failed or + 'data' not in opn1 + + - name: Showing config + oxlorg.opnsense.frr_diagnostic: + target: 'generalrunningconfig' + register: opn2 + failed_when: > + opn2.failed or + 'data' not in opn2 diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_general.yml new file mode 100644 index 0000000..b821df7 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_general.yml @@ -0,0 +1,96 @@ +--- + +- name: Testing FRR general settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring + oxlorg.opnsense.frr_general: + enabled: true + profile: 'traditional' + log: true + log_level: 'notifications' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.frr_general: + enabled: true + profile: 'datacenter' + log: true + log_level: 'emergencies' + snmp_agentx: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_general: + profile: 'datacenter' + log: true + log_level: 'emergencies' + snmp_agentx: true + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_general: + profile: 'datacenter' + log: true + log_level: 'emergencies' + snmp_agentx: true + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_general: + profile: 'datacenter' + log: true + log_level: 'emergencies' + snmp_agentx: true + enabled: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_general: + enabled: false + profile: 'traditional' + carp: false + snmp_agentx: false + log: true + log_level: 'notifications' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_general.yml new file mode 100644 index 0000000..5616294 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_general.yml @@ -0,0 +1,76 @@ +--- + +- name: Testing FRR-OSPFv3 general settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf3_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring + oxlorg.opnsense.frr_ospf3_general: + id: '10.0.0.1' + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.frr_ospf3_general: + id: '10.0.1.1' + enabled: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf3_general: + id: '10.0.1.1' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf3_general: + id: '10.0.1.1' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf3_general: + id: '10.0.1.1' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf3_general: + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_interface.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_interface.yml new file mode 100644 index 0000000..2318eb8 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_interface.yml @@ -0,0 +1,182 @@ +--- + +- name: Testing FRR-OSPFv3 interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.frr_ospf3_interface: + match_fields: ['interface'] + + oxlorg.opnsense.list: + target: 'frr_ospf3_interface' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf3_interface: + interface: 'lan' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid cost + oxlorg.opnsense.frr_ospf3_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 4300000000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid cost_demoted + oxlorg.opnsense.frr_ospf3_interface: + interface: 'lan' + area: '0.0.0.0' + field2: 70000 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf3_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 250 + cost_demoted: 1000 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf3_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf3_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf3_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf3_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf3_interface: + interface: 'opt1' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + hello_interval: 7000 + dead_interval: 5000 + retransmit_interval: 66 + transmit_delay: 60 + priority: 30 + network_type: 'point-to-point' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf3_interface: + interface: 'opt1' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + hello_interval: 7000 + dead_interval: 5000 + retransmit_interval: 66 + transmit_delay: 60 + priority: 30 + network_type: 'point-to-point' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf3_interface: + interface: 'opt1' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf3_interface: + interface: "{{ item }}" + state: 'absent' + loop: + - 'opt1' + - 'lan' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_network.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_network.yml new file mode 100644 index 0000000..36dc47d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_network.yml @@ -0,0 +1,245 @@ +--- + +- name: Testing FRR OSPFV3 Networks + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.frr_ospf3_network: + match_fields: ['ip', 'mask'] + + oxlorg.opnsense.list: + target: 'frr_ospf3_network' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid network mask + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 1337 + area: '0.0.0.0' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid network + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::1' + mask: 64 + area: '0.0.0.0' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of non-existent prefix-list + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + area: '0.0.0.0' + prefix_list_in: 'DOES-NOT-EXIST' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + area: '0.0.0.0' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + area: '0.0.0.1' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + area: '0.0.0.1' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + area: '0.0.0.1' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + area: '0.0.0.1' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8:1::' + mask: 64 + area: '0.0.10.1' + area_range: '2001:db8::/56' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8:1::' + mask: 64 + area: '0.0.10.1' + area_range: '2001:db8::/56' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8:1::' + mask: 64 + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf3_network: + ip: "{{ item.ip }}" + mask: "{{ item.mask }}" + state: 'absent' + loop: + - {'ip': '2001:db8:1::', 'mask': 64} + - {'ip': '2001:db8::', 'mask': 64} + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR OSPFV3 Networks - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.frr_ospf3_network: + match_fields: ['ip', 'mask'] + + oxlorg.opnsense.list: + target: 'frr_ospf3_network' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre3 + failed_when: > + 'data' not in opn_pre3 or + opn_pre3.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy prefix-list + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_3_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + when: not ansible_check_mode + + - name: Adding network linked to prefix-list + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + area: '0.0.0.0' + prefix_list_in: 'ANSIBLE_TEST_3_1' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Cleanup networks + oxlorg.opnsense.frr_ospf3_network: + ip: '2001:db8::' + mask: 64 + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup prefix-list + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_prefix_list.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_prefix_list.yml new file mode 100644 index 0000000..294c2ec --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_prefix_list.yml @@ -0,0 +1,158 @@ +--- + +- name: Testing FRR-OSPFV3 prefix lists + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf3_prefix_list' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid sequence-number + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 100 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 20 + action: 'permit' + network: '10.0.0.0/24' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_2' + seq: 12 + action: 'deny' + network: '10.0.10.0/24' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_2' + seq: 12 + action: 'deny' + network: '10.0.10.0/24' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf3_prefix_list: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_redistribution.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_redistribution.yml new file mode 100644 index 0000000..c69e453 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_redistribution.yml @@ -0,0 +1,218 @@ +--- + +- name: Testing FRR OSPFV3 Redistribution + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf3_redistribution' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid redistribute + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: ansible + register: opn_fail1 + failed_when: not opn_fail1.failed + + + - name: Adding 1 - failing because of non-existent route-map + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: bgp + route_map: 'DOES-NOT-EXIST' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: bgp + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_2' + redistribute: bgp + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_2' + redistribute: bgp + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf3_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR OSPFV3 Redistribution - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf3_redistribution' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre2 + failed_when: > + 'data' not in opn_pre2 or + opn_pre2.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy route-map + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_3_1' + id: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding redistribution linked to route-map + oxlorg.opnsense.frr_ospf3_redistribution: + description: 'ANSIBLE_TEST_2_1' + redistribute: bgp + route_map: 'ANSIBLE_TEST_3_1' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Cleanup redistributions + oxlorg.opnsense.frr_ospf3_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + when: not ansible_check_mode + + - name: Cleanup route-map + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_route_map.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_route_map.yml new file mode 100644 index 0000000..eff1e9f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf3_route_map.yml @@ -0,0 +1,227 @@ +--- + +- name: Testing FRR OSPFV3 Route-Maps + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf3_route_map' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid id + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 700 + action: 'permit' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of non-existent prefix-list + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 50 + action: 'permit' + prefix_list: 'DOES-NOT-EXIST' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 50 + action: 'permit' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_2' + id: 65 + action: 'permit' + set: 'local-preference 300' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_2' + id: 65 + action: 'permit' + set: 'local-preference 300' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf3_route_map: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR OSPFV3 Route-Maps - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf3_route_map' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre3 + failed_when: > + 'data' not in opn_pre3 or + opn_pre3.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy prefix-list + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_2_1' + network: '10.0.10.0/24' + seq: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding route-map linked to prefix-list + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_2_1' + prefix_list: ['ANSIBLE_TEST_2_1'] + id: 65 + action: 'permit' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Cleanup route-maps + oxlorg.opnsense.frr_ospf3_route_map: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup prefix-list + oxlorg.opnsense.frr_ospf3_prefix_list: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_general.yml new file mode 100644 index 0000000..ddc2855 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_general.yml @@ -0,0 +1,107 @@ +--- + +- name: Testing FRR-OSPF general settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid cost + oxlorg.opnsense.frr_ospf_general: + cost: 50000000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid originate_metric + oxlorg.opnsense.frr_ospf_general: + originate_metric: 17000000 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring + oxlorg.opnsense.frr_ospf_general: + id: '10.0.0.1' + cost: 250 + passive_ints: ['lan'] + originate: true + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.frr_ospf_general: + id: '10.0.1.1' + cost: 300 + passive_ints: ['lan'] + originate: true + originate_metric: 1000 + enabled: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf_general: + id: '10.0.1.1' + cost: 300 + passive_ints: ['lan'] + originate: true + originate_metric: 1000 + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf_general: + id: '10.0.1.1' + cost: 300 + passive_ints: ['lan'] + originate: true + originate_metric: 1000 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf_general: + id: '10.0.1.1' + cost: 300 + passive_ints: ['lan'] + originate: true + originate_metric: 1000 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf_general: + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_interface.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_interface.yml new file mode 100644 index 0000000..e26cfe2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_interface.yml @@ -0,0 +1,188 @@ +--- + +- name: Testing FRR-OSPF interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.frr_ospf_interface: + match_fields: ['interface'] + + oxlorg.opnsense.list: + target: 'frr_ospf_interface' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf_interface: + interface: 'lan' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid cost + oxlorg.opnsense.frr_ospf_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 4300000000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid cost_demoted + oxlorg.opnsense.frr_ospf_interface: + interface: 'lan' + area: '0.0.0.0' + field2: 70000 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 250 + cost_demoted: 1000 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf_interface: + interface: 'lan' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf_interface: + interface: 'opt1' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + hello_interval: 7000 + dead_interval: 5000 + retransmit_interval: 66 + transmit_delay: 60 + priority: 30 + network_type: 'point-to-point' + auth_type: 'message-digest' + auth_key: "{{ 'random' | hash('md5') }}" + auth_key_id: 5 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf_interface: + interface: 'opt1' + area: '0.0.0.0' + cost: 500 + cost_demoted: 2000 + hello_interval: 7000 + dead_interval: 5000 + retransmit_interval: 66 + transmit_delay: 60 + priority: 30 + network_type: 'point-to-point' + auth_type: 'message-digest' + auth_key: "{{ 'random' | hash('md5') }}" + auth_key_id: 5 + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf_interface: + interface: 'opt1' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf_interface: + interface: "{{ item }}" + state: 'absent' + loop: + - 'opt1' + - 'lan' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_network.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_network.yml new file mode 100644 index 0000000..d9aea29 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_network.yml @@ -0,0 +1,245 @@ +--- + +- name: Testing FRR OSPF Networks + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.frr_ospf_network: + match_fields: ['ip', 'mask'] + + oxlorg.opnsense.list: + target: 'frr_ospf_network' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.1.0' + mask: 30 + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid network mask + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.1.0' + mask: 1337 + area: '0.0.0.0' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid network + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.0.5' + mask: 30 + area: '0.0.0.0' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of non-existent prefix-list + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.1.0' + mask: 30 + area: '0.0.0.0' + prefix_list_in: 'DOES-NOT-EXIST' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.1.0' + mask: 30 + area: '0.0.0.0' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.1.0' + mask: 30 + area: '0.0.0.1' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.1.0' + mask: 30 + area: '0.0.0.1' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.1.0' + mask: 30 + area: '0.0.0.1' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.1.0' + mask: 30 + area: '0.0.0.1' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.2.0' + mask: 24 + area: '0.0.10.1' + area_range: '192.168.0.0/18' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.2.0' + mask: 24 + area: '0.0.10.1' + area_range: '192.168.0.0/18' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.2.0' + mask: 24 + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf_network: + ip: "{{ item.ip }}" + mask: "{{ item.mask }}" + state: 'absent' + loop: + - {'ip': '10.0.1.0', 'mask': 30} + - {'ip': '10.0.2.0', 'mask': 24} + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR OSPF Networks - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.frr_ospf_network: + match_fields: ['ip', 'mask'] + + oxlorg.opnsense.list: + target: 'frr_ospf_network' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre3 + failed_when: > + 'data' not in opn_pre3 or + opn_pre3.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy prefix-list + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_3_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + when: not ansible_check_mode + + - name: Adding network linked to prefix-list + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.3.0' + mask: 30 + area: '0.0.0.0' + prefix_list_in: 'ANSIBLE_TEST_3_1' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Cleanup networks + oxlorg.opnsense.frr_ospf_network: + ip: '10.0.3.0' + mask: 30 + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup prefix-list + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_prefix_list.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_prefix_list.yml new file mode 100644 index 0000000..ba3433d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_prefix_list.yml @@ -0,0 +1,158 @@ +--- + +- name: Testing FRR-OSPF prefix lists + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf_prefix_list' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid sequence-number + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 100 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 20 + action: 'permit' + network: '10.0.0.0/24' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_1' + seq: 25 + action: 'permit' + network: '10.0.1.0/24' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_2' + seq: 12 + action: 'deny' + network: '10.0.10.0/24' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_2' + seq: 12 + action: 'deny' + network: '10.0.10.0/24' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf_prefix_list: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_redistribution.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_redistribution.yml new file mode 100644 index 0000000..78f5105 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_redistribution.yml @@ -0,0 +1,218 @@ +--- + +- name: Testing FRR OSPF Redistribution + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf_redistribution' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid redistribute + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: ansible + register: opn_fail1 + failed_when: not opn_fail1.failed + + + - name: Adding 1 - failing because of non-existent route-map + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: bgp + route_map: 'DOES-NOT-EXIST' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: bgp + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_1' + redistribute: connected + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_2' + redistribute: bgp + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_2' + redistribute: bgp + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR OSPF Redistribution - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf_redistribution' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre2 + failed_when: > + 'data' not in opn_pre2 or + opn_pre2.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy route-map + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_3_1' + id: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding redistribution linked to route-map + oxlorg.opnsense.frr_ospf_redistribution: + description: 'ANSIBLE_TEST_2_1' + redistribute: bgp + route_map: 'ANSIBLE_TEST_3_1' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Cleanup redistributions + oxlorg.opnsense.frr_ospf_redistribution: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + when: not ansible_check_mode + + - name: Cleanup route-map + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_route_map.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_route_map.yml new file mode 100644 index 0000000..2403c0f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_ospf_route_map.yml @@ -0,0 +1,227 @@ +--- + +- name: Testing FRR OSPF Route-Maps + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf_route_map' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid id + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 700 + action: 'permit' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of non-existent prefix-list + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 50 + action: 'permit' + prefix_list: 'DOES-NOT-EXIST' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 50 + action: 'permit' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_1' + id: 45 + action: 'deny' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_2' + id: 65 + action: 'permit' + set: 'local-preference 300' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_2' + id: 65 + action: 'permit' + set: 'local-preference 300' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_ospf_route_map: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing FRR OSPF Route-Maps - linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_ospf_route_map' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre3 + failed_when: > + 'data' not in opn_pre3 or + opn_pre3.data | length != 0 + when: not ansible_check_mode + + - name: Adding dummy prefix-list + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_2_1' + network: '10.0.10.0/24' + seq: 50 + action: 'permit' + when: not ansible_check_mode + + - name: Adding route-map linked to prefix-list + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_2_1' + prefix_list: ['ANSIBLE_TEST_2_1'] + id: 65 + action: 'permit' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Cleanup route-maps + oxlorg.opnsense.frr_ospf_route_map: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup prefix-list + oxlorg.opnsense.frr_ospf_prefix_list: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean2 + failed_when: > + 'data' not in opn_clean2 or + opn_clean2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_rip.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_rip.yml new file mode 100644 index 0000000..e997c27 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/frr_rip.yml @@ -0,0 +1,100 @@ +--- + +- name: Testing RIP + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'frr_rip' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: opn_pre1.failed + + - name: Configuring - failing because of invalid version number + oxlorg.opnsense.frr_rip: + version: 3 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid metric + oxlorg.opnsense.frr_rip: + metric: 50 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring + oxlorg.opnsense.frr_rip: + passive_ints: ['lan'] + redistribute: ['static'] + networks: ['10.0.10.0/24'] + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.frr_rip: + passive_ints: ['lan'] + redistribute: ['static', 'ospf'] + networks: ['10.0.11.0/24'] + metric: 6 + enabled: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.frr_rip: + passive_ints: ['lan'] + redistribute: ['static', 'ospf'] + networks: ['10.0.11.0/24'] + metric: 6 + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.frr_rip: + passive_ints: ['lan'] + redistribute: ['static', 'ospf'] + networks: ['10.0.11.0/24'] + metric: 6 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.frr_rip: + passive_ints: ['lan'] + redistribute: ['static', 'ospf'] + networks: ['10.0.11.0/24'] + metric: 6 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.frr_rip: + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/gateway.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/gateway.yml new file mode 100644 index 0000000..2ea2252 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/gateway.yml @@ -0,0 +1,123 @@ +--- + +# NOTE: +# The gateway tests will not work correctly if the LAN network mismatches. +# You can provide your GW IPs via env-vars: `TEST_FIREWALL_GW1` and `TEST_FIREWALL_GW2` + +- name: Testing Gateways + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'gateway' + + oxlorg.opnsense.gateway: + match_fields: ['name'] + + vars: + pre_existing_gws: 2 + gw: + ip1: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL_GW1') | default('172.17.1.69', true) }}" + ip2: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL_GW2') | default('172.17.1.70', true) }}" + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Removing - does not exist + oxlorg.opnsense.gateway: + name: 'Test_GW' + state: 'absent' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + + - name: Adding 1 - failing because of invalid gateway + oxlorg.opnsense.gateway: + name: 'ANSIBLE_TEST_1' + interface: 'lan' + gateway: 999.999.999.999 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.gateway: + name: 'ANSIBLE_TEST_1' + interface: 'lan' + gateway: "{{ gw.ip1 }}" + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Adding 2 + oxlorg.opnsense.gateway: + name: 'ANSIBLE_TEST_2' + description: 'xyz' + interface: 'lan' + gateway: "{{ gw.ip2 }}" + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Disabling 1 + oxlorg.opnsense.gateway: + name: 'ANSIBLE_TEST_1' + interface: 'lan' + gateway: "{{ gw.ip1 }}" + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.gateway: + name: 'ANSIBLE_TEST_1' + interface: 'lan' + gateway: "{{ gw.ip1 }}" + enabled: false + register: opn7 + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.gateway: + name: 'ANSIBLE_TEST_1' + interface: 'lan' + gateway: "{{ gw.ip1 }}" + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing gateways + oxlorg.opnsense.list: + register: opn1 + failed_when: > + 'data' not in opn1 or + opn1.data | length != (pre_existing_gws + 2) + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.gateway: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + + - name: Listing gateways + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != pre_existing_gws diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/group.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/group.yml new file mode 100644 index 0000000..93e1973 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/group.yml @@ -0,0 +1,264 @@ +--- + +- name: Testing Access Group + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'group' + + tasks: + - name: Cleanup + oxlorg.opnsense.group: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | selectattr('scope', 'eq', 'user') | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.group: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Removing 1 - failing because of system group + oxlorg.opnsense.group: + name: 'admins' + state: 'absent' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid value + oxlorg.opnsense.group: + name: 'TOO_LONG_MAX_32_AAAAAAAAAAAAAAAAA' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_1 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_1 + description: ANSIBLE_TEST_1_1 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + source_net: 127.0.0.1/24 + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + source_net: 127.0.0.1/24 + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Changing 2 - grant privileges + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + source_net: 127.0.0.1/24 + privilege: + - user-config-readonly + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Changing 2 - grant privileges - nothing changed + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + source_net: 127.0.0.1/24 + privilege: + - user-config-readonly + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Changing 2 - revoking privileges + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + source_net: 127.0.0.1/24 + privilege: [] + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Changing 2 - revoking privileges - nothing changed + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + source_net: 127.0.0.1/24 + privilege: [] + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.group: + name: ANSIBLE_TEST_1_2 + state: 'absent' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn11 + failed_when: > + 'data' not in opn11 or + opn11.data | selectattr('scope', 'eq', 'user') | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.group: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | selectattr('scope', 'eq', 'user')| length != 0 + when: not ansible_check_mode + +- name: Testing Access Group Membership + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'group' + + tasks: + - name: Adding Dummy User + oxlorg.opnsense.user: + name: '{{ item }}' + loop: + - ANSIBLE_TEST_2_1 + - ANSIBLE_TEST_2_2 + + - name: Adding 1 + oxlorg.opnsense.group: + name: ANSIBLE_TEST_2_1 + member: ANSIBLE_TEST_2_1 + register: opn12 + failed_when: > + opn12.failed or + not opn12.changed + + - name: Changing 1 + oxlorg.opnsense.group: + name: ANSIBLE_TEST_2_1 + member: + - ANSIBLE_TEST_2_1 + - ANSIBLE_TEST_2_2 + register: opn13 + failed_when: > + opn13.failed or + not opn13.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.group: + name: ANSIBLE_TEST_2_1 + member: + - ANSIBLE_TEST_2_1 + - ANSIBLE_TEST_2_2 + register: opn14 + failed_when: > + opn14.failed or + opn14.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.group: + name: ANSIBLE_TEST_2_1 + member: [] + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.group: + name: ANSIBLE_TEST_2_1 + member: [] + register: opn16 + failed_when: > + opn16.failed or + opn16.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.group: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + when: not ansible_check_mode + + - name: Cleanup Dummy User + oxlorg.opnsense.user: + name: '{{ item }}' + state: 'absent' + loop: + - ANSIBLE_TEST_2_1 + - ANSIBLE_TEST_2_2 + diff: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_acl.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_acl.yml new file mode 100644 index 0000000..09a54f5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_acl.yml @@ -0,0 +1,169 @@ +--- + +- name: Testing HAProxy ACL management + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_acl' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid name + oxlorg.opnsense.haproxy_acl: + name: '' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of missing expression + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_1' + description: 'Test ACL 1' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Creating HAProxy ACL with host matching + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_1' + description: 'Test ACL for host matching' + expression: 'hdr' + hdr: 'example.com' + negate: false + case_sensitive: false + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Creating HAProxy ACL with path matching + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_2' + description: 'Test ACL for path matching' + expression: 'path_beg' + path_beg: '/api' + negate: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Creating HAProxy ACL with source metrics + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_3' + description: 'Test ACL for connection rate limit' + expression: 'src_conn_rate' + src_conn_rate: 100 + src_conn_rate_comparison: 'gt' + negate: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Creating HAProxy ACL with custom header + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_4' + description: 'Test ACL for custom header' + expression: 'cust_hdr' + cust_hdr_name: 'X-API-Key' + cust_hdr: 'secret123' + case_sensitive: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Changing ACL settings + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test ACL for host matching' + expression: 'hdr' + hdr: 'modified.example.com' + negate: false + case_sensitive: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling HAProxy ACL + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test ACL for host matching' + expression: 'hdr' + hdr: 'modified.example.com' + negate: true + case_sensitive: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Disabling HAProxy ACL - nothing changed + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test ACL for host matching' + expression: 'hdr' + hdr: 'modified.example.com' + negate: true + case_sensitive: true + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Enabling HAProxy ACL + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test ACL for host matching' + expression: 'hdr' + hdr: 'modified.example.com' + negate: false + case_sensitive: true + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test ACL for host matching' + expression: 'hdr' + hdr: 'modified.example.com' + negate: false + case_sensitive: true + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_acl: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + - 'ANSIBLE_TEST_ACL_1' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_action.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_action.yml new file mode 100644 index 0000000..d517405 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_action.yml @@ -0,0 +1,198 @@ +--- + +- name: Testing HAProxy Action management + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_action' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid name + oxlorg.opnsense.haproxy_action: + name: '' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Configuring - failing because of missing type + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_1' + description: 'Test Action 1' + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + # Create prerequisite ACL for testing + - name: Create test ACL for Action testing + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_ACL_1' + description: 'Test ACL for Action' + expression: 'hdr' + hdr: 'test.example.com' + register: test_acl + when: not ansible_check_mode + + - name: Creating HAProxy Action with deny + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_1' + description: 'Test Action for deny' + test_type: 'if' + type: 'http-request_deny' + operator: 'and' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Creating HAProxy Action with ACL link + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_2' + description: 'Test Action with ACL' + test_type: 'if' + linked_acls: ['ANSIBLE_TEST_ACL_1'] + operator: 'and' + type: 'http-request_allow' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Creating HAProxy Action with header manipulation + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_3' + description: 'Test Action for header add' + test_type: 'if' + type: 'http-request_add-header' + http_request_add_header_name: 'X-Custom-Header' + http_request_add_header_content: 'TestValue123' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Creating HAProxy Action with variable setting + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_4' + description: 'Test Action for set variable' + test_type: 'if' + type: 'http-request_set-var' + http_request_set_var_scope: 'txn' + http_request_set_var_name: 'test_var' + http_request_set_var_expr: 'str(authenticated)' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Changing Action settings + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test Action for deny' + test_type: 'unless' + type: 'http-request_deny' + operator: 'or' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Changing Action operator + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test Action for deny' + test_type: 'if' + type: 'http-request_deny' + operator: 'and' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Changing Action operator - nothing changed + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test Action for deny' + test_type: 'if' + type: 'http-request_deny' + operator: 'and' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Changing Action back to original + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_1' + description: 'Test Action for deny' + test_type: 'if' + type: 'http-request_deny' + operator: 'and' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_1' + description: 'Test Action for deny' + test_type: 'if' + type: 'http-request_deny' + operator: 'and' + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Cleanup Action 1 + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup Action 2 + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup Action 3 + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_3' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup Action 4 + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_1_4' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup test ACL + oxlorg.opnsense.haproxy_acl: + name: 'ANSIBLE_TEST_ACL_1' + expression: 'path_beg' + state: 'absent' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_cpu.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_cpu.yml new file mode 100644 index 0000000..4d1a37f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_cpu.yml @@ -0,0 +1,172 @@ +--- + +- name: Testing HAProxy CPU affinity rules + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_cpu' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Cleanup existing entries first + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_clean + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 + + - name: Removing - does not exist + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid name + oxlorg.opnsense.haproxy_cpu: + name: 'INVALID NAME WITH SPACES!' + thread_id: 'x1' + cpu_id: ['x0'] + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid thread_id (server-side) + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_1' + thread_id: 'x100' + cpu_id: ['x0'] + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_1' + enabled: true + thread_id: 'x1' + cpu_id: ['x0'] + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_1' + enabled: true + thread_id: 'x2' + cpu_id: ['x0', 'x1'] + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_1' + enabled: false + thread_id: 'x2' + cpu_id: ['x0', 'x1'] + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_1' + enabled: false + thread_id: 'x2' + cpu_id: ['x0', 'x1'] + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_1' + enabled: true + thread_id: 'x2' + cpu_id: ['x0', 'x1'] + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_2' + enabled: true + thread_id: 'x3' + cpu_id: ['x2'] + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_2' + enabled: true + thread_id: 'x3' + cpu_id: ['x2'] + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.haproxy_cpu: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_cpu: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_errorfile.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_errorfile.yml new file mode 100644 index 0000000..e5eb906 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_errorfile.yml @@ -0,0 +1,353 @@ +--- + +- name: Testing HAProxy Error file management + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_errorfile' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid name + oxlorg.opnsense.haproxy_errorfile: + name: '' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of missing content + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_1' + description: 'Test error file 1' + code: 'x503' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Creating HAProxy Error file for 503 + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_1' + description: 'Custom 503 Service Unavailable page' + code: 'x503' + content: | + HTTP/1.0 503 Service Unavailable + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Service Unavailable + + + +
+

Service Temporarily Unavailable

+

We're currently experiencing technical difficulties. Our team is working to resolve this issue as quickly as possible.

+

Please try again in a few minutes. If the problem persists, please contact our support team.

+ +
+ + + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Creating HAProxy Error file for 500 + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_2' + description: 'Custom 500 Internal Server Error page' + code: 'x500' + content: | + HTTP/1.0 500 Internal Server Error + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Internal Server Error + + + +
+

Oops! Something went wrong

+

An internal server error has occurred. We've been notified and are working to fix this issue.

+
HTTP 500
+
+ + + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Creating HAProxy Error file for 400 + oxlorg.opnsense.haproxy_errorfile: + name: ANSIBLE_TEST_1_3 + description: Custom 400 Bad Request page + code: x400 + content: | + HTTP/1.0 400 Bad Request + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Bad Request + + + +
+

400

+

Bad Request

+

The request could not be understood by the server.

+

Return to homepage

+
+ + + register: opn3 + failed_when: | + opn3.failed or not opn3.changed + + - name: Creating HAProxy Error file for 403 + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_4' + description: 'Custom 403 Forbidden page' + code: 'x403' + content: | + HTTP/1.0 403 Forbidden + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Access Forbidden + + + +
+

🚫 Access Forbidden

+

You don't have permission to access this resource on this server.

+

If you believe this is an error, please contact the website administrator.

+

HTTP 403

+
+ + + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Changing Error file content + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified custom 503 Service Unavailable page' + code: 'x503' + content: | + HTTP/1.0 503 Service Unavailable + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Maintenance Mode + + + +
+

🔧 Maintenance in Progress

+

We're currently performing scheduled maintenance to improve our service.

+

We'll be back online shortly. Thank you for your patience!

+
+ + + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Changing Error file code + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified custom 502 Bad Gateway page' + code: 'x502' + content: | + HTTP/1.0 502 Bad Gateway + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Bad Gateway + + +

502 Bad Gateway

+

The server received an invalid response from the upstream server.

+ + + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Changing Error file code - nothing changed + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified custom 502 Bad Gateway page' + code: 'x502' + content: | + HTTP/1.0 502 Bad Gateway + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Bad Gateway + + +

502 Bad Gateway

+

The server received an invalid response from the upstream server.

+ + + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Changing Error file back to original + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_1' + description: 'Custom 503 Service Unavailable page' + code: 'x503' + content: | + HTTP/1.0 503 Service Unavailable + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Service Unavailable + + +

Service Temporarily Unavailable

+

We're experiencing technical difficulties.

+ + + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_1' + description: 'Custom 503 Service Unavailable page' + code: 'x503' + content: | + HTTP/1.0 503 Service Unavailable + Content-Type: text/html + Cache-Control: no-cache + Connection: close + + + + + Service Unavailable + + +

Service Temporarily Unavailable

+

We're experiencing technical difficulties.

+ + + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Cleanup Error file 1 + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup Error file 2 + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup Error file 3 + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_3' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup Error file 4 + oxlorg.opnsense.haproxy_errorfile: + name: 'ANSIBLE_TEST_1_4' + state: 'absent' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_fcgi.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_fcgi.yml new file mode 100644 index 0000000..28dcd59 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_fcgi.yml @@ -0,0 +1,235 @@ +--- + +- name: Testing HAProxy FCGI application management + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_fcgi' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid name + oxlorg.opnsense.haproxy_fcgi: + name: '' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of missing docroot + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_1' + description: 'Test FCGI app 1' + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + # Create prerequisite actions for testing + - name: Create test action for FCGI testing + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_FCGI_ACTION_1' + description: 'Test Action for FCGI pass header' + test_type: 'if' + type: 'fcgi_pass_header' + fcgi_pass_header: 'X-FCGI-Test' + register: test_action1 + when: not ansible_check_mode + + - name: Create another test action for FCGI testing + oxlorg.opnsense.haproxy_action: + name: 'ANSIBLE_TEST_FCGI_ACTION_2' + description: 'Test Action for FCGI set param' + test_type: 'if' + type: 'fcgi_set_param' + fcgi_set_param: 'SCRIPT_NAME=/app' + register: test_action2 + when: not ansible_check_mode + + - name: Creating HAProxy FCGI application basic + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_1' + description: 'Test PHP-FPM application' + enabled: true + docroot: '/var/www/html' + index: 'index.php' + path_info: '^(/.+\.php)(/.*)?$' + log_stderr: false + keep_conn: true + get_values: false + mpxs_conns: false + max_reqs: 100 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Creating HAProxy FCGI application with stderr logging + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_2' + description: 'Test PHP-FPM application with logging' + enabled: true + docroot: '/var/www/api' + index: 'api.php' + path_info: '^(/.+\.php)(/.*)?$' + log_stderr: true + keep_conn: false + get_values: true + mpxs_conns: true + max_reqs: 50 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Creating HAProxy FCGI application with actions + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_3' + description: 'Test PHP-FPM application with actions' + enabled: true + docroot: '/var/www/app' + index: 'app.php' + linked_actions: ['ANSIBLE_TEST_FCGI_ACTION_1', 'ANSIBLE_TEST_FCGI_ACTION_2'] + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Creating HAProxy FCGI application minimal + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_4' + description: 'Test minimal FCGI application' + enabled: true + docroot: '/var/www/simple' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Changing FCGI application settings + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test PHP-FPM application' + enabled: true + docroot: '/var/www/html/public' + index: 'index.php' + path_info: '^(/.+\.php)(/.*)?$' + log_stderr: true + keep_conn: true + get_values: true + mpxs_conns: false + max_reqs: 200 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Disabling HAProxy FCGI application + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test PHP-FPM application' + enabled: false + docroot: '/var/www/html/public' + index: 'index.php' + path_info: '^(/.+\.php)(/.*)?$' + log_stderr: true + keep_conn: true + get_values: true + mpxs_conns: false + max_reqs: 200 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Disabling HAProxy FCGI application - nothing changed + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test PHP-FPM application' + enabled: false + docroot: '/var/www/html/public' + index: 'index.php' + path_info: '^(/.+\.php)(/.*)?$' + log_stderr: true + keep_conn: true + get_values: true + mpxs_conns: false + max_reqs: 200 + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Enabling HAProxy FCGI application + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test PHP-FPM application' + enabled: true + docroot: '/var/www/html/public' + index: 'index.php' + path_info: '^(/.+\.php)(/.*)?$' + log_stderr: true + keep_conn: true + get_values: true + mpxs_conns: false + max_reqs: 200 + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_fcgi: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test PHP-FPM application' + enabled: true + docroot: '/var/www/html/public' + index: 'index.php' + path_info: '^(/.+\.php)(/.*)?$' + log_stderr: true + keep_conn: true + get_values: true + mpxs_conns: false + max_reqs: 200 + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Cleanup FCGI app 1 + oxlorg.opnsense.haproxy_fcgi: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + when: not ansible_check_mode + + - name: Cleanup test action 1 + oxlorg.opnsense.haproxy_action: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_FCGI_ACTION_1' + - 'ANSIBLE_TEST_FCGI_ACTION_2' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_cache.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_cache.yml new file mode 100644 index 0000000..a015185 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_cache.yml @@ -0,0 +1,135 @@ +--- + +- name: Testing HAProxy general cache settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_general_cache' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid total_max_size + oxlorg.opnsense.haproxy_general_cache: + total_max_size: -1 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring basic cache settings + oxlorg.opnsense.haproxy_general_cache: + enabled: true + total_max_size: 16 + max_age: 120 + max_object_size: 8192 + process_vary: true + max_secondary_entries: 20 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing cache settings + oxlorg.opnsense.haproxy_general_cache: + enabled: true + total_max_size: 32 + max_age: 300 + max_object_size: 16384 + process_vary: false + max_secondary_entries: 50 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling HAProxy cache + oxlorg.opnsense.haproxy_general_cache: + enabled: false + total_max_size: 32 + max_age: 300 + max_object_size: 16384 + process_vary: false + max_secondary_entries: 50 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling HAProxy cache - nothing changed + oxlorg.opnsense.haproxy_general_cache: + enabled: false + total_max_size: 32 + max_age: 300 + max_object_size: 16384 + process_vary: false + max_secondary_entries: 50 + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling HAProxy cache + oxlorg.opnsense.haproxy_general_cache: + enabled: true + total_max_size: 32 + max_age: 300 + max_object_size: 16384 + process_vary: false + max_secondary_entries: 50 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Testing edge values + oxlorg.opnsense.haproxy_general_cache: + enabled: true + total_max_size: 1 + max_age: 1 + max_object_size: 1024 + process_vary: true + max_secondary_entries: 1 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_general_cache: + enabled: true + total_max_size: 1 + max_age: 1 + max_object_size: 1024 + process_vary: true + max_secondary_entries: 1 + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_general_cache: + enabled: false + total_max_size: 4 + max_age: 60 + process_vary: false + max_secondary_entries: 10 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_defaults.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_defaults.yml new file mode 100644 index 0000000..d9cedf2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_defaults.yml @@ -0,0 +1,110 @@ +--- + +- name: Testing HAProxy general defaults settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_general_defaults' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid timeout format + oxlorg.opnsense.haproxy_general_defaults: + timeout_client: 'invalid' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Configuring basic defaults settings + oxlorg.opnsense.haproxy_general_defaults: + max_connections: 2000 + max_connections_servers: 1000 + timeout_client: '60s' + timeout_connect: '10s' + timeout_check: '5s' + timeout_server: '60s' + retries: 5 + redispatch: 'x2' + init_addr: ['last', 'libc', 'none'] + custom_options: 'option httplog' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing defaults settings + oxlorg.opnsense.haproxy_general_defaults: + max_connections: 4000 + max_connections_servers: 2000 + timeout_client: '120s' + timeout_connect: '30s' + timeout_check: '10s' + timeout_server: '120s' + retries: 10 + redispatch: 'x1' + init_addr: ['libc', 'last'] + custom_options: 'option tcplog' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Testing edge values + oxlorg.opnsense.haproxy_general_defaults: + max_connections: 1 + max_connections_servers: 1 + timeout_client: '1s' + timeout_connect: '1s' + timeout_check: '1s' + timeout_server: '1s' + retries: 0 + redispatch: 'x-3' + init_addr: ['none'] + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_general_defaults: + max_connections: 1 + max_connections_servers: 1 + timeout_client: '1s' + timeout_connect: '1s' + timeout_check: '1s' + timeout_server: '1s' + retries: 0 + redispatch: 'x-3' + init_addr: ['none'] + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_general_defaults: + timeout_client: '30s' + timeout_connect: '30s' + timeout_server: '30s' + retries: 3 + redispatch: 'x-1' + init_addr: ['last', 'libc'] + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_logging.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_logging.yml new file mode 100644 index 0000000..926a624 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_logging.yml @@ -0,0 +1,92 @@ +--- + +- name: Testing HAProxy general logging settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_general_logging' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid length + oxlorg.opnsense.haproxy_general_logging: + length: 32 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid facility + oxlorg.opnsense.haproxy_general_logging: + facility: 'invalid_facility' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring basic logging settings + oxlorg.opnsense.haproxy_general_logging: + host: '192.168.1.100' + facility: 'local1' + level: 'warning' + length: 1024 + register: opn1 + failed_when: opn1.failed + + - name: Changing logging settings + oxlorg.opnsense.haproxy_general_logging: + host: '192.168.1.200:514' + facility: 'local2' + level: 'err' + length: 2048 + register: opn2 + failed_when: opn2.failed + + - name: Reset to minimal configuration + oxlorg.opnsense.haproxy_general_logging: + host: '127.0.0.1' + facility: 'local0' + level: 'info' + length: 1024 + register: opn3 + failed_when: opn3.failed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_general_logging: + host: '127.0.0.1' + facility: 'local0' + level: 'info' + length: 1024 + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Testing default values + oxlorg.opnsense.haproxy_general_logging: + host: '127.0.0.1' + facility: 'local0' + level: 'info' + register: opn5 + failed_when: opn5.failed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_general_logging: + host: '127.0.0.1' + facility: 'local0' + level: 'info' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_peers.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_peers.yml new file mode 100644 index 0000000..9ead2e4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_peers.yml @@ -0,0 +1,134 @@ +--- + +- name: Testing HAProxy general peers settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_general_peers' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid port + oxlorg.opnsense.haproxy_general_peers: + port1: 70000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring basic peers settings + oxlorg.opnsense.haproxy_general_peers: + enabled: true + name1: 'haproxy-primary.example.com' + listen1: '192.168.1.10' + port1: 9999 + name2: 'haproxy-secondary.example.com' + listen2: '192.168.1.11' + port2: 9999 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing peers settings + oxlorg.opnsense.haproxy_general_peers: + enabled: true + name1: 'haproxy-node1.example.com' + listen1: '10.0.0.10' + port1: 10000 + name2: 'haproxy-node2.example.com' + listen2: '10.0.0.11' + port2: 10000 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling HAProxy peers + oxlorg.opnsense.haproxy_general_peers: + enabled: false + name1: 'haproxy-node1.example.com' + listen1: '10.0.0.10' + port1: 10000 + name2: 'haproxy-node2.example.com' + listen2: '10.0.0.11' + port2: 10000 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling HAProxy peers - nothing changed + oxlorg.opnsense.haproxy_general_peers: + enabled: false + name1: 'haproxy-node1.example.com' + listen1: '10.0.0.10' + port1: 10000 + name2: 'haproxy-node2.example.com' + listen2: '10.0.0.11' + port2: 10000 + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling HAProxy peers + oxlorg.opnsense.haproxy_general_peers: + enabled: true + name1: 'haproxy-node1.example.com' + listen1: '10.0.0.10' + port1: 10000 + name2: 'haproxy-node2.example.com' + listen2: '10.0.0.11' + port2: 10000 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Testing single peer configuration + oxlorg.opnsense.haproxy_general_peers: + enabled: true + name1: 'haproxy-standalone.example.com' + listen1: '127.0.0.1' + port1: 1024 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_general_peers: + enabled: true + name1: 'haproxy-standalone.example.com' + listen1: '127.0.0.1' + port1: 1024 + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_general_peers: + enabled: false + port1: 1024 + port2: 1024 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_settings.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_settings.yml new file mode 100644 index 0000000..3ce8ae3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_settings.yml @@ -0,0 +1,140 @@ +--- + +- name: Testing HAProxy general settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_general_settings' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid hard_stop_after + oxlorg.opnsense.haproxy_general_settings: + hard_stop_after: 100000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid close_spread_time + oxlorg.opnsense.haproxy_general_settings: + close_spread_time: 5000 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring basic settings + oxlorg.opnsense.haproxy_general_settings: + enabled: true + graceful_stop: true + hard_stop_after: 60 + close_spread_time: 30 + seamless_reload: true + show_intro: false + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing settings + oxlorg.opnsense.haproxy_general_settings: + enabled: true + graceful_stop: false + hard_stop_after: 120 + close_spread_time: 60 + seamless_reload: false + show_intro: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling HAProxy + oxlorg.opnsense.haproxy_general_settings: + enabled: false + graceful_stop: false + hard_stop_after: 120 + close_spread_time: 60 + seamless_reload: false + show_intro: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling HAProxy - nothing changed + oxlorg.opnsense.haproxy_general_settings: + enabled: false + graceful_stop: false + hard_stop_after: 120 + close_spread_time: 60 + seamless_reload: false + show_intro: true + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling HAProxy + oxlorg.opnsense.haproxy_general_settings: + enabled: true + graceful_stop: false + hard_stop_after: 120 + close_spread_time: 60 + seamless_reload: false + show_intro: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Testing edge values + oxlorg.opnsense.haproxy_general_settings: + enabled: true + graceful_stop: true + hard_stop_after: 0 + close_spread_time: 0 + seamless_reload: true + show_intro: false + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_general_settings: + enabled: true + graceful_stop: true + hard_stop_after: 0 + close_spread_time: 0 + seamless_reload: true + show_intro: false + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_general_settings: + enabled: false + graceful_stop: true + seamless_reload: false + show_intro: true + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_stats.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_stats.yml new file mode 100644 index 0000000..88e3232 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_stats.yml @@ -0,0 +1,185 @@ +--- + +- name: Testing HAProxy general stats settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_general_stats' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Create test users for stats testing + oxlorg.opnsense.haproxy_user: + name: "{{ item.name }}" + description: "{{ item.description }}" + password: "{{ item.password }}" + enabled: true + loop: + - {name: 'stats_admin', description: 'Stats admin user', password: 'password123'} + - {name: 'stats_monitor', description: 'Stats monitor user', password: 'monitor123'} + when: not ansible_check_mode + + - name: Create test groups for stats testing + oxlorg.opnsense.haproxy_group: + name: "{{ item.name }}" + description: "{{ item.description }}" + members: "{{ item.members }}" + enabled: true + loop: + - {name: 'stats_admins', description: 'Stats admins group', members: ['stats_admin']} + - {name: 'stats_operators', description: 'Stats operators group', members: ['stats_monitor']} + when: not ansible_check_mode + + - name: Configuring - failing because of invalid port + oxlorg.opnsense.haproxy_general_stats: + port: 70000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring basic stats settings + oxlorg.opnsense.haproxy_general_stats: + enabled: true + port: 8823 + remote_enabled: true + remote_bind: ['0.0.0.0:8823'] + auth_enabled: true + users: ['admin:password123', 'monitor:monitor123'] + allowed_users: ['stats_admin', 'stats_monitor'] + allowed_groups: ['stats_admins', 'stats_operators'] + custom_options: 'stats enable' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Changing stats settings with Prometheus + oxlorg.opnsense.haproxy_general_stats: + enabled: true + port: 8824 + remote_enabled: false + auth_enabled: false + prometheus_enabled: true + prometheus_bind: ['*:8405', '127.0.0.1:8405'] + prometheus_path: '/prometheus-metrics' + custom_options: 'stats refresh 10s' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Disabling HAProxy stats + oxlorg.opnsense.haproxy_general_stats: + enabled: false + port: 8824 + remote_enabled: false + auth_enabled: false + prometheus_enabled: false + prometheus_bind: ['*:8405'] + prometheus_path: '/prometheus-metrics' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling HAProxy stats - nothing changed + oxlorg.opnsense.haproxy_general_stats: + enabled: false + port: 8824 + remote_enabled: false + auth_enabled: false + prometheus_enabled: false + prometheus_bind: ['*:8405'] + prometheus_path: '/prometheus-metrics' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling HAProxy stats + oxlorg.opnsense.haproxy_general_stats: + enabled: true + port: 8824 + remote_enabled: false + auth_enabled: false + prometheus_enabled: false + prometheus_bind: ['*:8405'] + prometheus_path: '/prometheus-metrics' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Testing minimal configuration + oxlorg.opnsense.haproxy_general_stats: + enabled: true + port: 8822 + remote_enabled: false + auth_enabled: false + prometheus_enabled: false + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_general_stats: + enabled: true + port: 8822 + remote_enabled: false + auth_enabled: false + prometheus_enabled: false + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Cleanup stats configuration + oxlorg.opnsense.haproxy_general_stats: + enabled: false + port: 8822 + remote_enabled: false + auth_enabled: false + prometheus_enabled: false + prometheus_bind: ['*:8404'] + prometheus_path: '/metrics' + when: not ansible_check_mode + + - name: Cleanup test groups + oxlorg.opnsense.haproxy_group: + name: "{{ item }}" + state: 'absent' + loop: + - 'stats_admins' + - 'stats_operators' + when: not ansible_check_mode + + - name: Cleanup test users + oxlorg.opnsense.haproxy_user: + name: "{{ item }}" + state: 'absent' + loop: + - 'stats_admin' + - 'stats_monitor' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_tuning.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_tuning.yml new file mode 100644 index 0000000..ee76d38 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_general_tuning.yml @@ -0,0 +1,91 @@ +--- + +- name: Testing HAProxy general tuning settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_general_tuning' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid max_connections + oxlorg.opnsense.haproxy_general_tuning: + max_connections: 20000000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid nbthread + oxlorg.opnsense.haproxy_general_tuning: + nbthread: 2048 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring basic tuning settings + oxlorg.opnsense.haproxy_general_tuning: + root: false + max_connections: 10000 + nbthread: 4 + resolvers_prefer: 'ipv4' + ssl_server_verify: 'required' + max_dh_size: 2048 + buffer_size: 32768 + spread_checks: 5 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing tuning settings + oxlorg.opnsense.haproxy_general_tuning: + root: false + max_connections: 5000 + nbthread: 2 + resolvers_prefer: 'ipv6' + ssl_server_verify: 'none' + max_dh_size: 4096 + buffer_size: 16384 + spread_checks: 3 + bogus_proxy_enabled: true + lua_max_mem: 100 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Nothing changed + oxlorg.opnsense.haproxy_general_tuning: + root: false + max_connections: 5000 + nbthread: 2 + resolvers_prefer: 'ipv6' + ssl_server_verify: 'none' + max_dh_size: 4096 + buffer_size: 16384 + spread_checks: 3 + bogus_proxy_enabled: true + lua_max_mem: 100 + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_general_tuning: + root: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_group.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_group.yml new file mode 100644 index 0000000..51af9f0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_group.yml @@ -0,0 +1,166 @@ +--- + +- name: Testing HAProxy group management + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_group' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Create test users first + oxlorg.opnsense.haproxy_user: + name: "{{ item.name }}" + description: "{{ item.description }}" + password: "{{ item.password }}" + enabled: true + loop: + - {name: 'groupuser1', description: 'Group test user 1', password: 'password1'} + - {name: 'groupuser2', description: 'Group test user 2', password: 'password2'} + - {name: 'groupuser3', description: 'Group test user 3', password: 'password3'} + when: not ansible_check_mode + + - name: Configuring - failing because of invalid name + oxlorg.opnsense.haproxy_group: + name: '' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Creating HAProxy group + oxlorg.opnsense.haproxy_group: + name: 'testgroup1' + description: 'Test group 1' + members: ['groupuser1', 'groupuser2'] + add_userlist: true + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Creating another HAProxy group + oxlorg.opnsense.haproxy_group: + name: 'testgroup2' + description: 'Test group 2' + members: ['groupuser3'] + add_userlist: false + enabled: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing group settings + oxlorg.opnsense.haproxy_group: + name: 'testgroup1' + description: 'Modified test group 1' + members: ['groupuser1', 'groupuser2', 'groupuser3'] + add_userlist: true + enabled: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Disabling HAProxy group + oxlorg.opnsense.haproxy_group: + name: 'testgroup1' + description: 'Modified test group 1' + members: ['groupuser1', 'groupuser2', 'groupuser3'] + add_userlist: true + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling HAProxy group - nothing changed + oxlorg.opnsense.haproxy_group: + name: 'testgroup1' + description: 'Modified test group 1' + members: ['groupuser1', 'groupuser2', 'groupuser3'] + add_userlist: true + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling HAProxy group + oxlorg.opnsense.haproxy_group: + name: 'testgroup1' + description: 'Modified test group 1' + members: ['groupuser1', 'groupuser2', 'groupuser3'] + add_userlist: true + enabled: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_group: + name: 'testgroup1' + description: 'Modified test group 1' + members: ['groupuser1', 'groupuser2', 'groupuser3'] + add_userlist: true + enabled: true + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Testing empty group + oxlorg.opnsense.haproxy_group: + name: 'emptygroup' + description: 'Empty test group' + members: [] + add_userlist: false + enabled: true + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Cleanup groups + oxlorg.opnsense.haproxy_group: + name: "{{ item }}" + state: 'absent' + loop: + - 'testgroup1' + - 'testgroup2' + - 'emptygroup' + when: not ansible_check_mode + + - name: Cleanup test users + oxlorg.opnsense.haproxy_user: + name: "{{ item }}" + state: 'absent' + loop: + - 'groupuser1' + - 'groupuser2' + - 'groupuser3' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_lua.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_lua.yml new file mode 100644 index 0000000..bc26fbb --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_lua.yml @@ -0,0 +1,260 @@ +--- + +- name: Testing HAProxy Lua script management + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_lua' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid name + oxlorg.opnsense.haproxy_lua: + name: '' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Configuring - failing because of missing content + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_1' + description: 'Test Lua script 1' + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Creating HAProxy Lua script with ID filename + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_1' + description: 'Test Lua script for authentication' + enabled: true + preload: true + filename_scheme: 'id' + content: | + function auth_check(txn) + local headers = txn.http:req_get_headers() + if headers["authorization"] then + core.Info("Authorization header found") + return "AUTHORIZED" + else + core.Info("No authorization header") + return "UNAUTHORIZED" + end + end + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Creating HAProxy Lua script with name filename + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_2' + description: 'Test Lua script for logging' + enabled: true + preload: false + filename_scheme: 'name' + content: | + function request_logger(txn) + local method = txn.sf:method() + local path = txn.sf:path() + local src_ip = txn.sf:src() + core.Info("Request: " .. method .. " " .. path .. " from " .. src_ip) + end + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Creating HAProxy Lua script for rate limiting + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_3' + description: 'Test Lua script for rate limiting' + enabled: true + preload: true + filename_scheme: 'id' + content: | + local rate_limiter = {} + + function rate_limit_check(txn) + local src_ip = txn.sf:src() + local current_time = core.now() + + if not rate_limiter[src_ip] then + rate_limiter[src_ip] = {count = 1, timestamp = current_time} + return "ALLOWED" + end + + local limit_data = rate_limiter[src_ip] + if current_time - limit_data.timestamp > 60 then + rate_limiter[src_ip] = {count = 1, timestamp = current_time} + return "ALLOWED" + end + + if limit_data.count >= 100 then + return "RATE_LIMITED" + end + + limit_data.count = limit_data.count + 1 + return "ALLOWED" + end + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Changing Lua script settings + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test Lua script for authentication' + enabled: true + preload: false + filename_scheme: 'name' + content: | + function auth_check_v2(txn) + local headers = txn.http:req_get_headers() + local auth_header = headers["authorization"] + if auth_header and string.find(auth_header, "Bearer ") then + core.Info("Bearer token found") + return "AUTHORIZED" + else + core.Info("No valid bearer token") + return "UNAUTHORIZED" + end + end + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling HAProxy Lua script + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test Lua script for authentication' + enabled: false + preload: false + filename_scheme: 'name' + content: | + function auth_check_v2(txn) + local headers = txn.http:req_get_headers() + local auth_header = headers["authorization"] + if auth_header and string.find(auth_header, "Bearer ") then + core.Info("Bearer token found") + return "AUTHORIZED" + else + core.Info("No valid bearer token") + return "UNAUTHORIZED" + end + end + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Disabling HAProxy Lua script - nothing changed + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test Lua script for authentication' + enabled: false + preload: false + filename_scheme: 'name' + content: | + function auth_check_v2(txn) + local headers = txn.http:req_get_headers() + local auth_header = headers["authorization"] + if auth_header and string.find(auth_header, "Bearer ") then + core.Info("Bearer token found") + return "AUTHORIZED" + else + core.Info("No valid bearer token") + return "UNAUTHORIZED" + end + end + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Enabling HAProxy Lua script + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test Lua script for authentication' + enabled: true + preload: false + filename_scheme: 'name' + content: | + function auth_check_v2(txn) + local headers = txn.http:req_get_headers() + local auth_header = headers["authorization"] + if auth_header and string.find(auth_header, "Bearer ") then + core.Info("Bearer token found") + return "AUTHORIZED" + else + core.Info("No valid bearer token") + return "UNAUTHORIZED" + end + end + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_1' + description: 'Modified test Lua script for authentication' + enabled: true + preload: false + filename_scheme: 'name' + content: | + function auth_check_v2(txn) + local headers = txn.http:req_get_headers() + local auth_header = headers["authorization"] + if auth_header and string.find(auth_header, "Bearer ") then + core.Info("Bearer token found") + return "AUTHORIZED" + else + core.Info("No valid bearer token") + return "UNAUTHORIZED" + end + end + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Cleanup Lua script 1 + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup Lua script 2 + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup Lua script 3 + oxlorg.opnsense.haproxy_lua: + name: 'ANSIBLE_TEST_1_3' + state: 'absent' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_maintenance.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_maintenance.yml new file mode 100644 index 0000000..9034003 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_maintenance.yml @@ -0,0 +1,84 @@ +--- + +- name: Testing HAProxy maintenance settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_maintenance' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring maintenance jobs + oxlorg.opnsense.haproxy_maintenance: + sync_certs: true + reload_service: false + restart_service: false + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing maintenance settings + oxlorg.opnsense.haproxy_maintenance: + sync_certs: false + reload_service: true + restart_service: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Enabling all maintenance jobs + oxlorg.opnsense.haproxy_maintenance: + sync_certs: true + reload_service: true + restart_service: true + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_maintenance: + sync_certs: true + reload_service: true + restart_service: true + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Disabling all maintenance jobs + oxlorg.opnsense.haproxy_maintenance: + sync_certs: false + reload_service: false + restart_service: false + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_maintenance: + sync_certs: false + reload_service: false + restart_service: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_user.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_user.yml new file mode 100644 index 0000000..69da998 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/haproxy_user.yml @@ -0,0 +1,123 @@ +--- + +- name: Testing HAProxy user management + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'haproxy_user' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid name + oxlorg.opnsense.haproxy_user: + name: '' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Creating HAProxy user + oxlorg.opnsense.haproxy_user: + name: 'testuser1' + description: 'Test user 1' + password: 'testpassword123' + enabled: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Creating another HAProxy user + oxlorg.opnsense.haproxy_user: + name: 'testuser2' + description: 'Test user 2' + password: 'testpassword456' + enabled: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing user settings + oxlorg.opnsense.haproxy_user: + name: 'testuser1' + description: 'Modified test user 1' + password: 'newpassword123' + enabled: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Disabling HAProxy user + oxlorg.opnsense.haproxy_user: + name: 'testuser1' + description: 'Modified test user 1' + password: 'newpassword123' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling HAProxy user - nothing changed + oxlorg.opnsense.haproxy_user: + name: 'testuser1' + description: 'Modified test user 1' + password: 'newpassword123' + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling HAProxy user + oxlorg.opnsense.haproxy_user: + name: 'testuser1' + description: 'Modified test user 1' + password: 'newpassword123' + enabled: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.haproxy_user: + name: 'testuser1' + description: 'Modified test user 1' + password: 'newpassword123' + enabled: true + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.haproxy_user: + name: "{{ item }}" + state: 'absent' + loop: + - 'testuser1' + - 'testuser2' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/hasync_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/hasync_general.yml new file mode 100644 index 0000000..2afb012 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/hasync_general.yml @@ -0,0 +1,145 @@ +--- + +- name: Testing HASync General + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'hasync_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Default Setting + oxlorg.opnsense.hasync_general: + + - name: Default Setting - nothing changed + oxlorg.opnsense.hasync_general: + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + when: not ansible_check_mode + + - name: Setting General + oxlorg.opnsense.hasync_general: + preempt: true + disconnect_ppps: true + pfsync_interface: 'lan' + pfsync_peer_ip: '4.4.4.4' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing General + oxlorg.opnsense.hasync_general: + preempt: true + disconnect_ppps: true + pfsync_interface: 'lan' + pfsync_peer_ip: '4.4.4.5' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Changing General - nothing changed + oxlorg.opnsense.hasync_general: + preempt: true + disconnect_ppps: true + pfsync_interface: 'lan' + pfsync_peer_ip: '4.4.4.5' + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.hasync_general: + when: not ansible_check_mode + + - name: Setting Config Sync + oxlorg.opnsense.hasync_general: + synchronize_to_ip: '4.4.4.4' + username: 'alice' + password: 'secret' + syncitems: + - aliases + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Changing Config Sync + oxlorg.opnsense.hasync_general: + synchronize_to_ip: '4.4.4.5' + username: 'alice' + password: 'secret2' + syncitems: + - aliases + - rules + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Changing Config Sync - nothing changed + oxlorg.opnsense.hasync_general: + synchronize_to_ip: '4.4.4.5' + username: 'alice' + password: 'secret2' + syncitems: + - aliases + - rules + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Setting Config Sync - update password + oxlorg.opnsense.hasync_general: + synchronize_to_ip: '4.4.4.5' + username: 'alice' + password: 'secret3' + syncitems: + - aliases + - rules + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Setting Config Sync - do not update password + oxlorg.opnsense.hasync_general: + synchronize_to_ip: '4.4.4.5' + username: 'alice' + password: 'secret' + update_password: on_create + syncitems: + - aliases + - rules + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.hasync_general: + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/hasync_service.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/hasync_service.yml new file mode 100644 index 0000000..962193b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/hasync_service.yml @@ -0,0 +1,136 @@ +--- + +- name: Testing HASync Service + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.hasync_service: + ignore_version_mismatch: true + + vars: + hasync_peer: "{{ lookup('ansible.builtin.env', 'TEST_HASYNC_PEER') | default(false) }}" + hasync_username: "{{ lookup('ansible.builtin.env', 'TEST_HASYNC_USERNAME') }}" + hasync_password: "{{ lookup('ansible.builtin.env', 'TEST_HASYNC_PASSWORD') }}" + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Restart all - failing because of missing peer + oxlorg.opnsense.hasync_service: + action: restart + register: opn_pre1 + failed_when: > + not opn_pre1.failed + + - name: Setup incorrect peer + oxlorg.opnsense.hasync_general: + synchronize_to_ip: "{{ hasync_peer }}" + username: "{{ hasync_username }}" + password: "invalid{{ hasync_password }}" + syncitems: + - aliases + - rules + + - name: Restart all - failing because of inaccessible peer + oxlorg.opnsense.hasync_service: + action: restart + register: opn_pre2 + failed_when: > + not opn_pre2.failed + + - name: Setup peer + oxlorg.opnsense.hasync_general: + synchronize_to_ip: "{{ hasync_peer }}" + username: "{{ hasync_username }}" + password: "{{ hasync_password }}" + syncitems: + - aliases + - rules + when: hasync_peer | bool + + - name: Restart all + oxlorg.opnsense.hasync_service: + action: restart + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: hasync_peer | bool + + - name: Stop login - failing because of unstopable service + oxlorg.opnsense.hasync_service: + name: login + action: stop + register: opn2 + failed_when: > + not opn2.failed + when: hasync_peer | bool + + - name: Stop ntpd + oxlorg.opnsense.hasync_service: + name: ntpd + action: stop + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: hasync_peer | bool + + - name: Pause for 5 seconds + ansible.builtin.pause: + seconds: 5 + when: hasync_peer | bool + + - name: Stop ntpd - nothing changed + oxlorg.opnsense.hasync_service: + name: ntpd + action: stop + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: hasync_peer | bool + + - name: Start ntpd + oxlorg.opnsense.hasync_service: + name: ntpd + action: start + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: hasync_peer | bool + + - name: Pause for 5 seconds + ansible.builtin.pause: + seconds: 5 + when: hasync_peer | bool + + - name: Start ntpd - nothing changed + oxlorg.opnsense.hasync_service: + name: ntpd + action: start + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: hasync_peer | bool + + - name: Restart ntpd + oxlorg.opnsense.hasync_service: + name: ntpd + action: restart + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: hasync_peer | bool + + - name: Cleanup peer + oxlorg.opnsense.hasync_general: diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_action.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_action.yml new file mode 100644 index 0000000..27d7aeb --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_action.yml @@ -0,0 +1,106 @@ +--- + +- name: Testing IDS Actions + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: IDS Service Start + oxlorg.opnsense.ids_action: + action: 'start' + register: opn1 + failed_when: opn1.failed + + - name: IDS Service Status + oxlorg.opnsense.ids_action: + action: 'status' + register: opn2 + failed_when: > + opn2.failed or + 'data' not in opn2 or + opn2.data | length == 0 + + - name: IDS Service Restart + oxlorg.opnsense.ids_action: + action: 'restart' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: IDS Get Alert Info - failing because of missing alert_id (client-side) + oxlorg.opnsense.ids_action: + action: 'get_alert_info' + register: opn4 + failed_when: not opn4.failed + + - name: IDS Get Alert Info + oxlorg.opnsense.ids_action: + action: 'get_alert_info' + alert_id: '1' + register: opn4 + failed_when: > + opn4.failed or + 'data' not in opn4 + + - name: IDS Get Alert Logs + oxlorg.opnsense.ids_action: + action: 'get_alert_logs' + register: opn5 + failed_when: > + opn5.failed or + 'data' not in opn5 + + - name: IDS Query Alerts + oxlorg.opnsense.ids_action: + action: 'query_alerts' + register: opn6 + failed_when: > + opn6.failed or + 'data' not in opn6 + + - name: IDS Reconfigure + oxlorg.opnsense.ids_action: + action: 'reconfigure' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: IDS Drop Alert Logs + oxlorg.opnsense.ids_action: + action: 'drop_alert_log' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + + - name: IDS Reload Rules + oxlorg.opnsense.ids_action: + action: 'reload_rules' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: IDS Update Rules + oxlorg.opnsense.ids_action: + action: 'update_rules' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + + - name: IDS Service Stop + oxlorg.opnsense.ids_action: + action: 'stop' + register: opn99 + failed_when: opn99.failed diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_general.yml new file mode 100644 index 0000000..241d0e2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_general.yml @@ -0,0 +1,148 @@ +--- + +- name: Testing IDS General + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ids_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Adding 1 - failing because of missing interfaces + oxlorg.opnsense.ids_general: + interfaces: [] + enabled: true + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid local-network + oxlorg.opnsense.ids_general: + interfaces: ['LAN'] + enabled: true + local_networks: ['invalid'] + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of too low packet-size + oxlorg.opnsense.ids_general: + interfaces: ['LAN'] + enabled: true + default_packet_size: 12 + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 - failing because of non-existing cron (server-side) + oxlorg.opnsense.ids_general: + interfaces: ['LAN'] + enabled: true + schedule: 'DOES-NOT-EXIST' + register: opn_fail4 + failed_when: not opn_fail4.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.ids_general: + interfaces: ['opt1'] + enabled: true + pattern_matcher: 'ac' + profile: 'low' + local_networks: ['10.0.0.0/16'] + log_rotate: 'daily' + log_retention: 14 + syslog: true + log_level: 'info' + + - name: Changing 1 + oxlorg.opnsense.ids_general: + interfaces: ['opt1'] + enabled: true + block: true + pattern_matcher: 'ac-bs' + profile: 'medium' + local_networks: ['10.0.0.0/16'] + log_rotate: 'daily' + log_retention: 10 + syslog: true + syslog_output: true + log_level: 'info' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.ids_general: + interfaces: ['opt1'] + enabled: false + block: true + pattern_matcher: 'ac-bs' + profile: 'medium' + local_networks: ['10.0.0.0/16'] + log_rotate: 'daily' + log_retention: 10 + syslog: true + syslog_output: true + log_level: 'info' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ids_general: + interfaces: ['opt1'] + enabled: false + block: true + pattern_matcher: 'ac-bs' + profile: 'medium' + local_networks: ['10.0.0.0/16'] + log_rotate: 'daily' + log_retention: 10 + syslog: true + syslog_output: true + log_level: 'info' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ids_general: + interfaces: ['opt1'] + block: true + pattern_matcher: 'ac-bs' + profile: 'medium' + local_networks: ['10.0.0.0/16'] + log_rotate: 'daily' + log_retention: 10 + syslog: true + syslog_output: true + log_level: 'info' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ids_general: + interfaces: ['opt1'] + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_policy.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_policy.yml new file mode 100644 index 0000000..67d74f7 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_policy.yml @@ -0,0 +1,151 @@ +--- + +- name: Testing IDS Policy + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ids_policy' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Adding 1 - failing because of non-existing/disabled ruleset + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + rulesets: "{{ item }}" + enabled: true + register: opn_fail1 + failed_when: not opn_fail1.failed + loop: + - 'DOES-NOT-EXIST' + - 'abuse.ch/SSL IP Blacklist' + + - name: Adding 1 + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + priority: 1 + rulesets: 'ET open/drop' + action: ['drop'] + new_action: 'alert' + rules: + classtype: ['misc-attack', 'bad-unknown'] + signature_severity: ['Minor'] + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + priority: 1 + rulesets: 'ET open/drop' + action: ['drop'] + new_action: 'alert' + rules: + classtype: ['misc-attack', 'bad-unknown'] + signature_severity: ['Minor'] + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + priority: 2 + rulesets: 'ET open/drop' + action: ['alert', 'drop'] + new_action: 'alert' + rules: + signature_severity: ['Minor'] + tag: 'Dshield' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + priority: 2 + rulesets: 'ET open/drop' + action: ['alert', 'drop'] + new_action: 'alert' + rules: + signature_severity: 'Minor' + tag: 'Dshield' + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + priority: 2 + rulesets: 'ET open/drop' + action: ['alert', 'drop'] + new_action: 'alert' + rules: + signature_severity: ['Minor'] + tag: 'Dshield' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + priority: 2 + rulesets: 'ET open/drop' + action: ['alert', 'drop'] + new_action: 'alert' + rules: + signature_severity: ['Minor'] + tag: 'Dshield' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + priority: 2 + rulesets: 'ET open/drop' + action: ['alert', 'drop'] + new_action: 'alert' + rules: + signature_severity: ['Minor'] + tag: 'Dshield' + enabled: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ids_policy: + description: 'ANSIBLE_TEST_1_1' + state: absent + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_policy_rule.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_policy_rule.yml new file mode 100644 index 0000000..47c977c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_policy_rule.yml @@ -0,0 +1,98 @@ +--- + +- name: Testing IDS Policy-Rule + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ids_policy_rule' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Adding 1 + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + action: 'alert' + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + action: 'alert' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + action: 'drop' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + action: 'drop' + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + action: 'drop' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + action: 'drop' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + action: 'drop' + enabled: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ids_policy_rule: + sid: 2400000 + state: absent + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_rule.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_rule.yml new file mode 100644 index 0000000..29108d1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_rule.yml @@ -0,0 +1,84 @@ +--- + +- name: Testing IDS Rule + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ids_rule' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Adding 1 - failing because of non-existing rule + oxlorg.opnsense.ids_rule: + sid: 13374206969 + enabled: true + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Changing 1 + oxlorg.opnsense.ids_rule: + sid: 2400000 + action: 'drop' + + - name: Disabling 1 + oxlorg.opnsense.ids_rule: + sid: 2400000 + action: 'drop' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ids_rule: + sid: 2400000 + action: 'drop' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ids_rule: + sid: 2400000 + action: 'drop' + enabled: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 - nothing changed + oxlorg.opnsense.ids_rule: + sid: 2400000 + action: 'drop' + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ids_rule: + sid: 2400000 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_ruleset.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_ruleset.yml new file mode 100644 index 0000000..96a98e3 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_ruleset.yml @@ -0,0 +1,66 @@ +--- + +- name: Testing IDS Ruleset + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ids_ruleset' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Adding 1 - failing because of non-existing ruleset + oxlorg.opnsense.ids_ruleset: + name: 'DOES-NOT-EXIST' + enabled: true + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Enabling 1 (used by other tests) + oxlorg.opnsense.ids_ruleset: + name: "{{ item }}" + timeout: 60 # download + loop: + - 'ET open/drop' + - 'OPNsense-App-detect/test' + + - name: Enabling 2 + oxlorg.opnsense.ids_ruleset: + name: 'ET open/compromised' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Enabling 2 - nothing changed + oxlorg.opnsense.ids_ruleset: + name: 'ET open/compromised' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Disabling 2 + oxlorg.opnsense.ids_ruleset: + name: 'ET open/compromised' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_user_rule.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_user_rule.yml new file mode 100644 index 0000000..48ae0e5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ids_user_rule.yml @@ -0,0 +1,162 @@ +--- + +- name: Testing IDS User-Rule + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ids_user_rule' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Removing 1 - does not exist + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + source_ip: '192.168.10.1' + destination_ip: '1.1.1.1' + ssl_fingerprint: 'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E' + action: 'alert' + bypass: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + source_ip: '192.168.10.1' + destination_ip: '1.1.1.1' + ssl_fingerprint: 'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E' + action: 'alert' + bypass: false + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Changing 1 - more + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + source_ip: '192.168.20.1' + destination_ip: '1.1.0.0' + ssl_fingerprint: 'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E' + action: 'pass' + bypass: true + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + source_ip: '192.168.20.1' + destination_ip: '1.1.0.0' + ssl_fingerprint: 'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E' + action: 'pass' + bypass: true + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + source_ip: '192.168.20.1' + destination_ip: '1.1.0.0' + ssl_fingerprint: 'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E' + action: 'pass' + bypass: true + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + source_ip: '192.168.20.1' + destination_ip: '1.1.0.0' + ssl_fingerprint: 'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E' + action: 'pass' + bypass: true + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + source_ip: '192.168.20.1' + destination_ip: '1.1.0.0' + ssl_fingerprint: 'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E' + action: 'pass' + bypass: true + enabled: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 - nothing changed + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + source_ip: '192.168.20.1' + destination_ip: '1.1.0.0' + ssl_fingerprint: 'B5:E1:B3:70:5E:7C:FF:EB:92:C4:29:E5:5B:AC:2F:AE:70:17:E9:9E' + action: 'pass' + bypass: true + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ids_user_rule: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_bridge.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_bridge.yml new file mode 100644 index 0000000..bac3dc2 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_bridge.yml @@ -0,0 +1,155 @@ +--- + +- name: Testing Bridge interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'interface_bridge' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.interface_bridge: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 + oxlorg.opnsense.interface_bridge: + description: 'ANSIBLE_TEST_1_1' + members: + - 'opt1' + - 'opt2' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.interface_bridge: + description: 'ANSIBLE_TEST_1_1' + members: 'opt1' + link_local: true + span_interfaces: 'opt2' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.interface_bridge: + description: 'ANSIBLE_TEST_1_1' + members: 'opt1' + link_local: true + span_interfaces: 'opt2' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.interface_bridge: + description: 'ANSIBLE_TEST_1_2' + members: + - 'opt1' + - 'opt2' + link_local: true + stp: true + stp_proto: 'stp' + stp_interfaces: 'opt1' + stp_max_age: 10 + stp_fwdelay: 10 + stp_hold: 5 + cache_size: 1000 + cache_timeout: 600 + edge_interfaces: 'opt1' + auto_edge_interfaces: 'opt2' + ptp_interfaces: 'opt1' + auto_ptp_interfaces: 'opt2' + static_interfaces: 'opt1' + private_interfaces: 'opt2' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.interface_bridge: + description: 'ANSIBLE_TEST_1_2' + members: + - 'opt1' + - 'opt2' + link_local: true + stp: true + stp_proto: 'stp' + stp_interfaces: 'opt1' + stp_max_age: 10 + stp_fwdelay: 10 + stp_hold: 5 + cache_size: 1000 + cache_timeout: 600 + edge_interfaces: 'opt1' + auto_edge_interfaces: 'opt2' + ptp_interfaces: 'opt1' + auto_ptp_interfaces: 'opt2' + static_interfaces: 'opt1' + private_interfaces: 'opt2' + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.interface_bridge: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.interface_bridge: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_gif.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_gif.yml new file mode 100644 index 0000000..d7287ba --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_gif.yml @@ -0,0 +1,138 @@ +--- + +- name: Testing GIF interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'interface_gif' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.interface_gif: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid tunnel_remote_net + oxlorg.opnsense.interface_gif: + description: 'ANSIBLE_TEST_1_1' + local_addr: 'lan' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.1' + tunnel_remote_addr: '10.0.0.2' + tunnel_remote_net: 300 + register: opn_fail1 + failed_when: not opn_fail1.failed or 'tunnel_remote_net' not in opn_fail1.msg + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.interface_gif: + description: 'ANSIBLE_TEST_1_1' + local_addr: 'lan' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.1' + tunnel_remote_addr: '10.0.0.2' + tunnel_remote_net: 30 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 + oxlorg.opnsense.interface_gif: + description: 'ANSIBLE_TEST_1_2' + local_addr: '192.168.1.100' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.1' + tunnel_remote_addr: '10.0.0.2' + tunnel_remote_net: 30 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 - changing + oxlorg.opnsense.interface_gif: + description: 'ANSIBLE_TEST_1_2' + local_addr: '192.168.1.100' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.5' + tunnel_remote_addr: '10.0.0.6' + tunnel_remote_net: 30 + ingress_filtering: false + ecn_friendly: true + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Adding 2 - nothing changed + oxlorg.opnsense.interface_gif: + description: 'ANSIBLE_TEST_1_2' + local_addr: '192.168.1.100' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.5' + tunnel_remote_addr: '10.0.0.6' + tunnel_remote_net: 30 + ingress_filtering: false + ecn_friendly: true + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.interface_gif: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn6 + failed_when: > + 'data' not in opn6 or + opn6.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.interface_gif: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_gre.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_gre.yml new file mode 100644 index 0000000..a8cbe7c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_gre.yml @@ -0,0 +1,134 @@ +--- + +- name: Testing GRE interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'interface_gre' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.interface_gre: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid tunnel_remote_net + oxlorg.opnsense.interface_gre: + description: 'ANSIBLE_TEST_1_1' + local_addr: 'lan' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.1' + tunnel_remote_addr: '10.0.0.2' + tunnel_remote_net: 300 + register: opn_fail1 + failed_when: not opn_fail1.failed or 'tunnel_remote_net' not in opn_fail1.msg + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.interface_gre: + description: 'ANSIBLE_TEST_1_1' + local_addr: 'lan' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.1' + tunnel_remote_addr: '10.0.0.2' + tunnel_remote_net: 30 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 + oxlorg.opnsense.interface_gre: + description: 'ANSIBLE_TEST_1_2' + local_addr: '192.168.1.100' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.1' + tunnel_remote_addr: '10.0.0.2' + tunnel_remote_net: 30 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 - changing + oxlorg.opnsense.interface_gre: + description: 'ANSIBLE_TEST_1_2' + local_addr: '192.168.1.100' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.5' + tunnel_remote_addr: '10.0.0.6' + tunnel_remote_net: 30 + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Adding 2 - nothing changed + oxlorg.opnsense.interface_gre: + description: 'ANSIBLE_TEST_1_2' + local_addr: '192.168.1.100' + remote_addr: '192.168.100.1' + tunnel_local_addr: '10.0.0.5' + tunnel_remote_addr: '10.0.0.6' + tunnel_remote_net: 30 + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.interface_gre: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn6 + failed_when: > + 'data' not in opn6 or + opn6.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.interface_gre: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_lagg.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_lagg.yml new file mode 100644 index 0000000..fcbabc6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_lagg.yml @@ -0,0 +1,93 @@ +--- + +- name: Testing LAGG interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'interface_lagg' + + vars: + if_lag: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL_LAGG_IF') | default('vtnet2', true) }}" + if_lag_ctn: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL_LAGG_CNT') | default('1', true) }}" + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.interface_lagg: + device: 'lagg99' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid interface (server-side) + oxlorg.opnsense.interface_lagg: + device: 'lagg0' + members: + - 'DOES-NOT-EXIST' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.interface_lagg: + device: 'lagg0' + description: 'ANSIBLE_TEST_1_1' + members: + - '{{ if_lag }}' + lagghash: l2 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.interface_lagg: + device: 'lagg0' + description: 'ANSIBLE_TEST_1_1' + members: + - '{{ if_lag }}' + lagghash: l2 + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.interface_lagg: + device: 'lagg0' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_loopback.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_loopback.yml new file mode 100644 index 0000000..2c2c05e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_loopback.yml @@ -0,0 +1,93 @@ +--- + +- name: Testing Loopback + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'interface_loopback' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.interface_loopback: + description: 'DOESNOTEXIST' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 + oxlorg.opnsense.interface_loopback: + description: 'ANSIBLE_TEST_1_1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 + oxlorg.opnsense.interface_loopback: + description: 'ANSIBLE_TEST_1_2' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.interface_loopback: + description: 'ANSIBLE_TEST_1_2' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.interface_loopback: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn5 + failed_when: > + 'data' not in opn5 or + opn5.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.interface_loopback: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vip.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vip.yml new file mode 100644 index 0000000..658c907 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vip.yml @@ -0,0 +1,196 @@ +--- + +- name: Testing VIPs + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'interface_vip' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.interface_vip: + interface: 'lan' + address: '192.168.0.1/30' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid interface (server-side) + oxlorg.opnsense.interface_vip: + interface: 'DOESNOTEXIST' + address: '192.168.1.1/30' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding 1 - failing because of missing CIDR + oxlorg.opnsense.interface_vip: + interface: 'lan' + address: '192.168.1.1' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.interface_vip: + interface: 'lan' + address: '192.168.1.1/30' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '192.168.2.1/24' + mode: 'carp' + vhid: 10 + password: 'top_secret' + advertising_base: 2 + advertising_skew: 1 + bind: false + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '192.168.2.1/24' + mode: 'carp' + vhid: 10 + password: 'top_secret' + advertising_base: 2 + advertising_skew: 1 + bind: false + register: opn6 + diff: true + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '192.168.2.1/24' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 3 - unicast peer + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '192.168.2.1/24' + mode: 'carp' + peer: '192.168.3.3' + vhid: 10 + password: 'top_secret' + advertising_base: 2 + advertising_skew: 1 + bind: false + register: opn_carpuni1 + failed_when: > + opn_carpuni1.failed or + not opn_carpuni1.changed + + - name: Removing 3 + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '192.168.2.1/24' + state: 'absent' + register: opn_carpuni2 + failed_when: > + opn_carpuni2.failed or + not opn_carpuni2.changed + when: not ansible_check_mode + + - name: Adding 4 - unicast peer6 + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '2001:db8::1/128' + mode: 'carp' + peer6: '2001:db8::3' + vhid: 10 + password: 'top_secret' + advertising_base: 2 + advertising_skew: 1 + bind: false + register: opn_carpuni3 + failed_when: > + opn_carpuni3.failed or + not opn_carpuni3.changed + + - name: Adding 4 - nothing changed + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '2001:db8::1/128' + mode: 'carp' + peer6: '2001:db8::3' + vhid: 10 + password: 'top_secret' + advertising_base: 2 + advertising_skew: 1 + bind: false + register: opn_carpuni4 + failed_when: > + opn_carpuni4.failed or + opn_carpuni4.changed + when: not ansible_check_mode + + - name: Removing 4 + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '2001:db8::1/128' + state: 'absent' + register: opn_carpuni4 + failed_when: > + opn_carpuni4.failed or + not opn_carpuni4.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.interface_vip: + interface: "{{ item.int }}" + address: "{{ item.ip }}" + state: 'absent' + loop: + - {int: 'lan', ip: '192.168.1.1/30'} + - {int: 'opt1', ip: '192.168.2.1/24'} + - {int: 'opt1', ip: '2001:db8::1/128'} + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vlan.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vlan.yml new file mode 100644 index 0000000..1fa0868 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vlan.yml @@ -0,0 +1,160 @@ +--- + +- name: Testing VLAN interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'interface_vlan' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid interface (server-side) + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_1' + interface: 'lan' + vlan: 100 + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding 1 - failing because of missing interface + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_1' + vlan: 100 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of invalid vlan-id + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_1' + interface: 'vtnet0' + vlan: 4100 + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding 1 + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_1' + interface: 'vtnet0' + vlan: 100 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 - failing because of duplicate vlan-id (server-side) + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_2' + interface: 'vtnet0' + vlan: 100 + register: opn_fail5 + failed_when: not opn_fail5.failed + when: not ansible_check_mode + + - name: Adding 2 - failing because of invalid device-name (server-side) + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_2' + interface: 'vtnet0' + vlan: 100 + device: 'vlan100' + register: opn_fail5 + failed_when: not opn_fail5.failed + when: not ansible_check_mode + + - name: Adding 2 - failing because of duplicate device-name (server-side) + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_2' + interface: 'vtnet0' + vlan: 100 + device: 'vlan0.100' + register: opn_fail5 + failed_when: not opn_fail5.failed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_2' + interface: 'vtnet0' + vlan: 101 + priority: 5 + device: 'vlan0.101' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_2' + interface: 'vtnet0' + vlan: 101 + priority: 5 + device: 'vlan0.101' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.interface_vlan: + description: 'ANSIBLE_TEST_1_2' + interface: 'vtnet0' + vlan: 101 + priority: 5 + device: 'vlan0.101' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.interface_vlan: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vxlan.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vxlan.yml new file mode 100644 index 0000000..075f8bc --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/interface_vxlan.yml @@ -0,0 +1,187 @@ +--- + +- name: Testing VXLAN interfaces + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'interface_vxlan' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.interface_vxlan: + id: 100 + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid interface (server-side) + oxlorg.opnsense.interface_vxlan: + id: 100 + local: '192.168.0.1' + interface: 'invalid' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding 1 - failing because of missing local-ip + oxlorg.opnsense.interface_vxlan: + id: 100 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of invalid local-ip + oxlorg.opnsense.interface_vxlan: + id: 100 + local: '192.168.0.1000' + interface: 'lan' + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding 1 - failing because of invalid remote-ip + oxlorg.opnsense.interface_vxlan: + id: 100 + local: '192.168.0.1' + remote: '192.168.1.1000' + interface: 'lan' + register: opn_fail5 + failed_when: not opn_fail5.failed + + - name: Adding 1 - failing because of invalid group-ip + oxlorg.opnsense.interface_vxlan: + id: 100 + local: '192.168.0.1' + group: '192.168.1.1000' + interface: 'lan' + register: opn_fail6 + failed_when: not opn_fail6.failed + + - name: Adding 1 - failing because of missing remote-address/mc-group (server-side) + oxlorg.opnsense.interface_vxlan: + id: 100 + local: '192.168.0.1' + interface: 'lan' + register: opn_fail7 + failed_when: not opn_fail7.failed + when: not ansible_check_mode + + - name: Adding 1 - failing because interface & remote-address are set (server-side) + oxlorg.opnsense.interface_vxlan: + id: 100 + local: '192.168.0.1' + remote: '192.168.2.1' + interface: 'lan' + register: opn_fail8 + failed_when: not opn_fail8.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.interface_vxlan: + id: 100 + local: '192.168.0.1' + remote: '192.168.2.1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 + oxlorg.opnsense.interface_vxlan: + id: 101 + local: '192.168.1.1' + group: '192.168.3.1' + interface: 'lan' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.interface_vxlan: + id: 101 + local: '192.168.1.1' + group: '192.168.3.1' + interface: 'lan' + register: opn6 + diff: true + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.interface_vxlan: + id: 101 + local: '192.168.1.1' + group: '192.168.3.1' + interface: 'lan' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 3 with ports + oxlorg.opnsense.interface_vxlan: + id: 102 + local: '192.168.4.1' + local_port: 10800 + remote: '192.168.5.1' + remote_port: 10801 + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + + - name: Change 3 ports + oxlorg.opnsense.interface_vxlan: + id: 102 + local: '192.168.4.1' + local_port: 10900 + remote: '192.168.5.1' + remote_port: 10901 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.interface_vxlan: + id: "{{ item }}" + state: 'absent' + loop: + - 100 + - 101 + - 102 + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_auth_local.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_auth_local.yml new file mode 100644 index 0000000..d593438 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_auth_local.yml @@ -0,0 +1,266 @@ +--- + +- name: Testing IPSec Local Authentication + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_auth_local' + + vars: + test1: + pub: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2OJtOwz6s6+v0UCcjiOA + eAQCaLZADjZ3kdc70/U6S1nzo6jWU6Aii/md+BLw0SLs4c5krNpo3zQ7dhZLpUIH + DZVvAMQnfi1kZmrInLdQYZ6/i+ilxHSGC+jm+7Uuz5rPotw4kICZ92PofE4+B3uJ + kS1PASbcJCbL2cidOyoatKLj38+9iQF54ZBVpOz2XNkuAG1lRPFmuQtc72MTyFzv + 02PlGWuZt3twDL9Hn61HRGmA3Wy3xto0y/ZKzEHZH+wiGV8wCM4kuxONxtzc/ezQ + ox6rLPKBPQkbXqCE5Y9rp7xlpcrYuuWIneyKepLlw6vHnRv7+y4LIwk658HjsKzi + OQIDAQAB + -----END PUBLIC KEY----- + priv: | + -----BEGIN RSA PRIVATE KEY----- + MIIEowIBAAKCAQEA2OJtOwz6s6+v0UCcjiOAeAQCaLZADjZ3kdc70/U6S1nzo6jW + U6Aii/md+BLw0SLs4c5krNpo3zQ7dhZLpUIHDZVvAMQnfi1kZmrInLdQYZ6/i+il + xHSGC+jm+7Uuz5rPotw4kICZ92PofE4+B3uJkS1PASbcJCbL2cidOyoatKLj38+9 + iQF54ZBVpOz2XNkuAG1lRPFmuQtc72MTyFzv02PlGWuZt3twDL9Hn61HRGmA3Wy3 + xto0y/ZKzEHZH+wiGV8wCM4kuxONxtzc/ezQox6rLPKBPQkbXqCE5Y9rp7xlpcrY + uuWIneyKepLlw6vHnRv7+y4LIwk658HjsKziOQIDAQABAoIBAQDAvwH6P7+x3wnu + enBntc9fWZjWfFmTB/7dgp2t8js7ahantM29BgyNv2oPZK3V/ybsSqOYJoabDu1l + Nc1GcdaY0NwUnz7F2QtoJbBh7lwmVZG/giOH41KZ1QMqYUvXvqqW/wXaDiBHBug/ + SL3Bop2Qgua6jFGKY1w5ERwCz5lqO9HLmaiaZtiiiejw0gKJI1/YKbmzpq+k5kxl + uamN/7FQTApQS2PN9FjqEaxcPuz8Yi+uEpjbVW75lkPmibVIHg7FXmXaz860Qw/8 + X/RckjeVivqq4V1KjDDhbRMNhJAytLTgo4r4/MaRdsYAX1yU9dWKPcGl78JeePm8 + 4kT4AUzxAoGBAPgwwsQLlONfKKWfBaAKLGM88N9qicrC8ptzSZWn7FqjrjL+wiIG + X0s88l/mzMPLCXqFg3RHSgdvISAfXx27TttpljGc28RENiZOB5tLbPsh7IM1BTrg + Ov2qNsxWO5xqxOxX/+tW0rov2uWM96tLrv+YI3KT1C1ifdX5FW3U3tG9AoGBAN+1 + fOgH/sqNqeE/gnvS25b3U9A/ZmdR13hOVss1v1r4Oa+tKmawYk/rdTuqW5nIoJ1V + EO/IwzXoOyVn+OYQaj3rn4E/ybRAy0AMRZhIrZbWIBX3MnP0xxGAHfFItWI+qeoy + mLz6VuAJauvOsAuL76wPYdd4q+MUC9/oJYFecFQtAoGAC5Sq4d7weZONH/1Fk+wl + mhPT4XjlKRLjoyFEA7msK6aLkFGW2WOWuroDTTpFv7UPoinsslZJPAORdiBAnfCJ + g29v1KzPDF9qb2sgq7xfP3CbypuEvPSNjByPJgW4DlplCeopRN/uQUXOXvuu6s1D + QyXkMYp4Ug3QdVWEDHXsV7kCgYBUk1/CtWsdlwtXzlP9jk1YuO7l92I2w5lLsYpc + z1gmA1yDz1sNcbfpcSJkSVbSQCiA8u0xSlyLH95kmPdfu2r/N/qYuc3/KNPuxfT1 + ytxd/1woEcnwTuWH90DavNteZkSE91YJdDeuAcF7nyutYd1d1n7uIIATnLuUjkbH + rzWWjQKBgHilCTPkvf+awlSTZTxomfJogZI8pPGBuKfEzqKrmGKNhqbunFJvg5IW + R6TK1xBlSgiaplTf+yBFbUHmlQ1Yc4CW0HGUAzRS/qaDeba5tmwKiNs+ekFSbw/m + 0Z0gyiFy6GY4g/BMNO7Kw/LqGOMVTl+cwUrpBdGQXMnhb9z/iKa8 + -----END RSA PRIVATE KEY----- + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing connection + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of missing cert/pubkey when using pubkey-auth + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + authentication: 'pubkey' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of missing eap-id when using eap-auth + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + authentication: 'eap_mschapv2' + eap_id: 'dummy' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 - failing because of invalid round + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + round: 11 + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding 1 - failing because of non-existent connection + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + connection: 'ANSIBLE_TEST_4_1' + register: opn_fail5 + failed_when: not opn_fail5.failed + when: not ansible_check_mode + + - name: Adding dummy connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_4_1' + when: not ansible_check_mode + + - name: Adding dummy cert + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_2_1' + public_key: "{{ test1.pub }}" + private_key: "{{ test1.priv }}" + when: not ansible_check_mode + + - name: Adding 1 (psk) + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + connection: 'ANSIBLE_TEST_4_1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + connection: 'ANSIBLE_TEST_4_1' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Changing 1 (pubkey) + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_4_1' + authentication: 'pubkey' + public_keys: 'ANSIBLE_TEST_2_1' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_4_1' + authentication: 'pubkey' + public_keys: 'ANSIBLE_TEST_2_1' + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_4_1' + authentication: 'pubkey' + public_keys: 'ANSIBLE_TEST_2_1' + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_4_1' + authentication: 'pubkey' + public_keys: 'ANSIBLE_TEST_2_1' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 (eap) + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_2' + connection: 'ANSIBLE_TEST_4_1' + authentication: 'eap-tls' + eap_id: 'dummy' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_2' + connection: 'ANSIBLE_TEST_4_1' + authentication: 'eap-tls' + eap_id: 'dummy' + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.ipsec_auth_local: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup auth + oxlorg.opnsense.ipsec_auth_local: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Cleanup connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_4_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup cert + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_auth_remote.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_auth_remote.yml new file mode 100644 index 0000000..de612b0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_auth_remote.yml @@ -0,0 +1,266 @@ +--- + +- name: Testing IPSec Remote Authentication + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_auth_remote' + + vars: + test1: + pub: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2OJtOwz6s6+v0UCcjiOA + eAQCaLZADjZ3kdc70/U6S1nzo6jWU6Aii/md+BLw0SLs4c5krNpo3zQ7dhZLpUIH + DZVvAMQnfi1kZmrInLdQYZ6/i+ilxHSGC+jm+7Uuz5rPotw4kICZ92PofE4+B3uJ + kS1PASbcJCbL2cidOyoatKLj38+9iQF54ZBVpOz2XNkuAG1lRPFmuQtc72MTyFzv + 02PlGWuZt3twDL9Hn61HRGmA3Wy3xto0y/ZKzEHZH+wiGV8wCM4kuxONxtzc/ezQ + ox6rLPKBPQkbXqCE5Y9rp7xlpcrYuuWIneyKepLlw6vHnRv7+y4LIwk658HjsKzi + OQIDAQAB + -----END PUBLIC KEY----- + priv: | + -----BEGIN RSA PRIVATE KEY----- + MIIEowIBAAKCAQEA2OJtOwz6s6+v0UCcjiOAeAQCaLZADjZ3kdc70/U6S1nzo6jW + U6Aii/md+BLw0SLs4c5krNpo3zQ7dhZLpUIHDZVvAMQnfi1kZmrInLdQYZ6/i+il + xHSGC+jm+7Uuz5rPotw4kICZ92PofE4+B3uJkS1PASbcJCbL2cidOyoatKLj38+9 + iQF54ZBVpOz2XNkuAG1lRPFmuQtc72MTyFzv02PlGWuZt3twDL9Hn61HRGmA3Wy3 + xto0y/ZKzEHZH+wiGV8wCM4kuxONxtzc/ezQox6rLPKBPQkbXqCE5Y9rp7xlpcrY + uuWIneyKepLlw6vHnRv7+y4LIwk658HjsKziOQIDAQABAoIBAQDAvwH6P7+x3wnu + enBntc9fWZjWfFmTB/7dgp2t8js7ahantM29BgyNv2oPZK3V/ybsSqOYJoabDu1l + Nc1GcdaY0NwUnz7F2QtoJbBh7lwmVZG/giOH41KZ1QMqYUvXvqqW/wXaDiBHBug/ + SL3Bop2Qgua6jFGKY1w5ERwCz5lqO9HLmaiaZtiiiejw0gKJI1/YKbmzpq+k5kxl + uamN/7FQTApQS2PN9FjqEaxcPuz8Yi+uEpjbVW75lkPmibVIHg7FXmXaz860Qw/8 + X/RckjeVivqq4V1KjDDhbRMNhJAytLTgo4r4/MaRdsYAX1yU9dWKPcGl78JeePm8 + 4kT4AUzxAoGBAPgwwsQLlONfKKWfBaAKLGM88N9qicrC8ptzSZWn7FqjrjL+wiIG + X0s88l/mzMPLCXqFg3RHSgdvISAfXx27TttpljGc28RENiZOB5tLbPsh7IM1BTrg + Ov2qNsxWO5xqxOxX/+tW0rov2uWM96tLrv+YI3KT1C1ifdX5FW3U3tG9AoGBAN+1 + fOgH/sqNqeE/gnvS25b3U9A/ZmdR13hOVss1v1r4Oa+tKmawYk/rdTuqW5nIoJ1V + EO/IwzXoOyVn+OYQaj3rn4E/ybRAy0AMRZhIrZbWIBX3MnP0xxGAHfFItWI+qeoy + mLz6VuAJauvOsAuL76wPYdd4q+MUC9/oJYFecFQtAoGAC5Sq4d7weZONH/1Fk+wl + mhPT4XjlKRLjoyFEA7msK6aLkFGW2WOWuroDTTpFv7UPoinsslZJPAORdiBAnfCJ + g29v1KzPDF9qb2sgq7xfP3CbypuEvPSNjByPJgW4DlplCeopRN/uQUXOXvuu6s1D + QyXkMYp4Ug3QdVWEDHXsV7kCgYBUk1/CtWsdlwtXzlP9jk1YuO7l92I2w5lLsYpc + z1gmA1yDz1sNcbfpcSJkSVbSQCiA8u0xSlyLH95kmPdfu2r/N/qYuc3/KNPuxfT1 + ytxd/1woEcnwTuWH90DavNteZkSE91YJdDeuAcF7nyutYd1d1n7uIIATnLuUjkbH + rzWWjQKBgHilCTPkvf+awlSTZTxomfJogZI8pPGBuKfEzqKrmGKNhqbunFJvg5IW + R6TK1xBlSgiaplTf+yBFbUHmlQ1Yc4CW0HGUAzRS/qaDeba5tmwKiNs+ekFSbw/m + 0Z0gyiFy6GY4g/BMNO7Kw/LqGOMVTl+cwUrpBdGQXMnhb9z/iKa8 + -----END RSA PRIVATE KEY----- + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing connection + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of missing cert/pubkey when using pubkey-auth + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + authentication: 'pubkey' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of missing eap-id when using eap-auth + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + authentication: 'eap_mschapv2' + eap_id: 'dummy' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 - failing because of invalid round + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + round: 11 + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding 1 - failing because of non-existent connection + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + connection: 'ANSIBLE_TEST_5_1' + register: opn_fail5 + failed_when: not opn_fail5.failed + when: not ansible_check_mode + + - name: Adding dummy connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_5_1' + when: not ansible_check_mode + + - name: Adding dummy cert + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_3_1' + public_key: "{{ test1.pub }}" + private_key: "{{ test1.priv }}" + when: not ansible_check_mode + + - name: Adding 1 (psk) + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + connection: 'ANSIBLE_TEST_5_1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + authentication: 'psk' + connection: 'ANSIBLE_TEST_5_1' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Changing 1 (pubkey) + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_5_1' + authentication: 'pubkey' + public_keys: 'ANSIBLE_TEST_3_1' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_5_1' + authentication: 'pubkey' + public_keys: 'ANSIBLE_TEST_3_1' + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_5_1' + authentication: 'pubkey' + public_keys: 'ANSIBLE_TEST_3_1' + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_5_1' + authentication: 'pubkey' + public_keys: 'ANSIBLE_TEST_3_1' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 (eap) + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_2' + connection: 'ANSIBLE_TEST_5_1' + authentication: 'eap-tls' + eap_id: 'dummy' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_2' + connection: 'ANSIBLE_TEST_5_1' + authentication: 'eap-tls' + eap_id: 'dummy' + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.ipsec_auth_remote: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup auth + oxlorg.opnsense.ipsec_auth_remote: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Cleanup connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_5_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup cert + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_cert.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_cert.yml new file mode 100644 index 0000000..72f0a64 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_cert.yml @@ -0,0 +1,190 @@ +--- + +- name: Testing IPSec certificates + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.ipsec_cert: + reload: true + + oxlorg.opnsense.list: + target: 'ipsec_cert' + + vars: + test1: + pub: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2OJtOwz6s6+v0UCcjiOA + eAQCaLZADjZ3kdc70/U6S1nzo6jWU6Aii/md+BLw0SLs4c5krNpo3zQ7dhZLpUIH + DZVvAMQnfi1kZmrInLdQYZ6/i+ilxHSGC+jm+7Uuz5rPotw4kICZ92PofE4+B3uJ + kS1PASbcJCbL2cidOyoatKLj38+9iQF54ZBVpOz2XNkuAG1lRPFmuQtc72MTyFzv + 02PlGWuZt3twDL9Hn61HRGmA3Wy3xto0y/ZKzEHZH+wiGV8wCM4kuxONxtzc/ezQ + ox6rLPKBPQkbXqCE5Y9rp7xlpcrYuuWIneyKepLlw6vHnRv7+y4LIwk658HjsKzi + OQIDAQAB + -----END PUBLIC KEY----- + priv: | + -----BEGIN RSA PRIVATE KEY----- + MIIEowIBAAKCAQEA2OJtOwz6s6+v0UCcjiOAeAQCaLZADjZ3kdc70/U6S1nzo6jW + U6Aii/md+BLw0SLs4c5krNpo3zQ7dhZLpUIHDZVvAMQnfi1kZmrInLdQYZ6/i+il + xHSGC+jm+7Uuz5rPotw4kICZ92PofE4+B3uJkS1PASbcJCbL2cidOyoatKLj38+9 + iQF54ZBVpOz2XNkuAG1lRPFmuQtc72MTyFzv02PlGWuZt3twDL9Hn61HRGmA3Wy3 + xto0y/ZKzEHZH+wiGV8wCM4kuxONxtzc/ezQox6rLPKBPQkbXqCE5Y9rp7xlpcrY + uuWIneyKepLlw6vHnRv7+y4LIwk658HjsKziOQIDAQABAoIBAQDAvwH6P7+x3wnu + enBntc9fWZjWfFmTB/7dgp2t8js7ahantM29BgyNv2oPZK3V/ybsSqOYJoabDu1l + Nc1GcdaY0NwUnz7F2QtoJbBh7lwmVZG/giOH41KZ1QMqYUvXvqqW/wXaDiBHBug/ + SL3Bop2Qgua6jFGKY1w5ERwCz5lqO9HLmaiaZtiiiejw0gKJI1/YKbmzpq+k5kxl + uamN/7FQTApQS2PN9FjqEaxcPuz8Yi+uEpjbVW75lkPmibVIHg7FXmXaz860Qw/8 + X/RckjeVivqq4V1KjDDhbRMNhJAytLTgo4r4/MaRdsYAX1yU9dWKPcGl78JeePm8 + 4kT4AUzxAoGBAPgwwsQLlONfKKWfBaAKLGM88N9qicrC8ptzSZWn7FqjrjL+wiIG + X0s88l/mzMPLCXqFg3RHSgdvISAfXx27TttpljGc28RENiZOB5tLbPsh7IM1BTrg + Ov2qNsxWO5xqxOxX/+tW0rov2uWM96tLrv+YI3KT1C1ifdX5FW3U3tG9AoGBAN+1 + fOgH/sqNqeE/gnvS25b3U9A/ZmdR13hOVss1v1r4Oa+tKmawYk/rdTuqW5nIoJ1V + EO/IwzXoOyVn+OYQaj3rn4E/ybRAy0AMRZhIrZbWIBX3MnP0xxGAHfFItWI+qeoy + mLz6VuAJauvOsAuL76wPYdd4q+MUC9/oJYFecFQtAoGAC5Sq4d7weZONH/1Fk+wl + mhPT4XjlKRLjoyFEA7msK6aLkFGW2WOWuroDTTpFv7UPoinsslZJPAORdiBAnfCJ + g29v1KzPDF9qb2sgq7xfP3CbypuEvPSNjByPJgW4DlplCeopRN/uQUXOXvuu6s1D + QyXkMYp4Ug3QdVWEDHXsV7kCgYBUk1/CtWsdlwtXzlP9jk1YuO7l92I2w5lLsYpc + z1gmA1yDz1sNcbfpcSJkSVbSQCiA8u0xSlyLH95kmPdfu2r/N/qYuc3/KNPuxfT1 + ytxd/1woEcnwTuWH90DavNteZkSE91YJdDeuAcF7nyutYd1d1n7uIIATnLuUjkbH + rzWWjQKBgHilCTPkvf+awlSTZTxomfJogZI8pPGBuKfEzqKrmGKNhqbunFJvg5IW + R6TK1xBlSgiaplTf+yBFbUHmlQ1Yc4CW0HGUAzRS/qaDeba5tmwKiNs+ekFSbw/m + 0Z0gyiFy6GY4g/BMNO7Kw/LqGOMVTl+cwUrpBdGQXMnhb9z/iKa8 + -----END RSA PRIVATE KEY----- + test2: + pub: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcwPIyXFbLDyvy18FFQkqcPnn/C8y + M4yJiLC9goySJ24D+KC4O817o+nILkChhXzk3zsl2HVw1ZBfP/FjS0aDyg== + -----END PUBLIC KEY----- + priv: | + -----BEGIN PRIVATE KEY----- + MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+v8hYa1Kz7cHLAOX + c0wvBaEu56igiTsdqM3BpjPACmGhRANCAARzA8jJcVssPK/LXwUVCSpw+ef8LzIz + jImIsL2CjJInbgP4oLg7zXuj6cguQKGFfOTfOyXYdXDVkF8/8WNLRoPK + -----END PRIVATE KEY----- + test2_mismatch: + pub: | + -----BEGIN PUBLIC KEY----- + MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEnjp/CepoZ91YPnD+MJG6KQ4TEW5b + LWp/f75EP04MDrQdQMW2FvOZcVLEbRJGazbaHAvNGJb2A8lU9O5T6GT1bw== + -----END PUBLIC KEY----- + + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Validation - failing because of invalid pub-key + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_1_1' + public_key: 'not-a-pubkey' + private_key: "{{ test1.priv }}" + register: opn3 + failed_when: not opn3.failed + + - name: Validation - failing because of invalid priv-key + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_1_1' + public_key: "{{ test1.pub }}" + private_key: 'not-a-privkey' + register: opn4 + failed_when: not opn4.failed + + - name: Validation - failing because of key-mismatch (server-side) + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_1_1' + public_key: "{{ test2_mismatch.pub }}" + private_key: "{{ test2.priv }}" + register: opn5 + failed_when: not opn5.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_1_1' + public_key: "{{ test1.pub }}" + private_key: "{{ test1.priv }}" + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_1_1' + public_key: "{{ test1.pub }}" + private_key: "{{ test1.priv }}" + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_1_2' + public_key: "{{ test2.pub }}" + private_key: "{{ test2.priv }}" + type: ecdsa + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Changing 1 + oxlorg.opnsense.ipsec_cert: + name: 'ANSIBLE_TEST_1_1' + public_key: "{{ test2.pub }}" + private_key: "{{ test2.priv }}" + type: ecdsa + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ipsec_cert: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_child.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_child.yml new file mode 100644 index 0000000..f55b791 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_child.yml @@ -0,0 +1,204 @@ +--- + +- name: Testing IPSec pools/networks + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_child' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Adding dummy connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_3_1' + when: not ansible_check_mode + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because no connection was provided + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + local_net: '192.168.1.0/28' + remote_net: '192.168.2.0/28' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because no networks were provided + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_3_1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.1.0/28' + remote_net: '192.168.2.0/28' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.1.0/28' + remote_net: '192.168.2.0/28' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.10.0/28' + remote_net: '192.168.11.0/28' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.10.0/28' + remote_net: '192.168.11.0/28' + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.10.0/28' + remote_net: '192.168.11.0/28' + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_1' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.10.0/28' + remote_net: '192.168.11.0/28' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_2' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.20.0/28' + remote_net: '192.168.21.0/28' + request_id: 200 + sha256_96: true + start_action: 'route' + close_action: 'trap' + dpd_action: 'start' + rekey_seconds: 1800 + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 - nothing changed + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_2' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.20.0/28' + remote_net: '192.168.21.0/28' + request_id: 200 + sha256_96: true + start_action: 'route' + close_action: 'trap' + dpd_action: 'start' + rekey_seconds: 1800 + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ipsec_child: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Cleanup dummy connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_connection.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_connection.yml new file mode 100644 index 0000000..af12ad0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_connection.yml @@ -0,0 +1,279 @@ +--- + +- name: Testing IPSec tunnels/connections + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_connection' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid proposal (server-side) + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_1' + proposals: 'INVALID-IP' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_1' + version: 'ikev2' + unique: 'replace' + proposals: ['default', 'aes256-sha256-modp2048', 'aes256gcm16-sha512-modp2048'] + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_1' + version: 'ikev2' + unique: 'replace' + proposals: ['default', 'aes256-sha256-modp2048', 'aes256gcm16-sha512-modp2048'] + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_1' + unique: 'replace' + proposals: 'aes256gcm16-sha512-modp2048' + reauth_seconds: 3600 + keying_tries: 5 + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Disabling 1 + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_1' + unique: 'replace' + proposals: 'aes256gcm16-sha512-modp2048' + reauth_seconds: 3600 + keying_tries: 5 + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed (w/ module alias) + oxlorg.opnsense.ipsec_tunnel: + name: 'ANSIBLE_TEST_1_1' + unique: 'replace' + proposals: 'aes256gcm16-sha512-modp2048' + reauth_seconds: 3600 + keying_tries: 5 + enabled: false + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_1' + unique: 'replace' + proposals: 'aes256gcm16-sha512-modp2048' + reauth_seconds: 3600 + keying_tries: 5 + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_1' + unique: 'replace' + proposals: 'aes256gcm16-sha512-modp2048' + reauth_seconds: 3600 + keying_tries: 5 + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_2' + unique: 'never' + version: 'ikev1' + reauth_seconds: 7200 + dpd_timeout_seconds: 7200 + dpd_delay_seconds: 7200 + over_seconds: 7200 + rekey_seconds: 7200 + keying_tries: 30 + encapsulation: true + mobike: false + aggressive: true + local_addresses: ['192.168.1.1'] + local_port: 4500 + remote_addresses: ['192.168.2.1', '192.168.3.1'] + remote_port: 4500 + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_2' + unique: 'never' + version: 'ikev1' + reauth_seconds: 7200 + dpd_timeout_seconds: 7200 + dpd_delay_seconds: 7200 + over_seconds: 7200 + rekey_seconds: 7200 + keying_tries: 30 + encapsulation: true + mobike: false + aggressive: true + local_addresses: ['192.168.1.1'] + local_port: 4500 + remote_addresses: ['192.168.2.1', '192.168.3.1'] + remote_port: 4500 + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ipsec_connection: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode + +- name: Testing IPSec connection to pool linking + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding dummy pool 1 + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_2_1' + network: '192.168.2.0/29' + when: not ansible_check_mode + + - name: Adding dummy pool 2 + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_2_2' + network: '192.168.3.0/29' + when: not ansible_check_mode + + - name: Adding connection linked to pool + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_2_1' + pools: ['ANSIBLE_TEST_2_1', 'ANSIBLE_TEST_2_2'] + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_2_1' + pools: ['ANSIBLE_TEST_2_2', 'ANSIBLE_TEST_2_1'] + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Changing linked pools + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_2_1' + pools: ['ANSIBLE_TEST_2_1'] + register: opn12 + failed_when: > + opn12.failed or + not opn12.changed + when: not ansible_check_mode + + - name: Cleanup connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup pools + oxlorg.opnsense.ipsec_pool: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + - 'ANSIBLE_TEST_2_2' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_general.yml new file mode 100644 index 0000000..9d83098 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_general.yml @@ -0,0 +1,241 @@ +--- + +- name: Testing IPSec General + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 + + - name: Configuring - General + oxlorg.opnsense.ipsec_general: + prefer_old_sa: true + disable_vpn_rules: true + passthrough_networks: + - 192.168.255.255/24 + authentication: + - 'Local Database' + local_group: admins + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Configuring - General - nothing changed + oxlorg.opnsense.ipsec_general: + prefer_old_sa: true + disable_vpn_rules: true + passthrough_networks: + - 192.168.255.255/24 + authentication: + - 'Local Database' + local_group: admins + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Configuring - eap-radius + oxlorg.opnsense.ipsec_general: + radius_accounting: true + radius_class_group: true + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Configuring - eap-radius - nothing changed + oxlorg.opnsense.ipsec_general: + radius_accounting: true + radius_class_group: true + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Configuring - xauth-pam + oxlorg.opnsense.ipsec_general: + pam_service: ansible + pam_session: true + pam_trim_email: false + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Configuring - xauth-pam - nothing changed + oxlorg.opnsense.ipsec_general: + pam_service: ansible + pam_session: true + pam_trim_email: false + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Configuring - Charon + oxlorg.opnsense.ipsec_general: + charon_max_ikev1_exchanges: 4 + charon_threads: 8 + charon_ikesa_table_size: 16 + charon_ikesa_table_segments: 2 + charon_ignore_acquire_ts: false + charon_make_before_break: true + charon_install_routes: true + charon_cisco_unity: true + retransmit_tries: 10 + retransmit_timeout: 10 + retransmit_base: 10 + retransmit_jitter: 10 + retransmit_limit: 10 + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Configuring - Charon - nothing changed + oxlorg.opnsense.ipsec_general: + charon_max_ikev1_exchanges: 4 + charon_threads: 8 + charon_ikesa_table_size: 16 + charon_ikesa_table_segments: 2 + charon_ignore_acquire_ts: false + charon_make_before_break: true + charon_install_routes: true + charon_cisco_unity: true + retransmit_tries: 10 + retransmit_timeout: 10 + retransmit_base: 10 + retransmit_jitter: 10 + retransmit_limit: 10 + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Configuring - Syslog + oxlorg.opnsense.ipsec_general: + syslog_log_name: false + syslog_log_level: true + syslog_app: 0 + syslog_asn: 0 + syslog_cfg: 0 + syslog_chd: 0 + syslog_dmn: 0 + syslog_enc: 0 + syslog_esp: 0 + syslog_ike: 0 + syslog_imc: 0 + syslog_imv: 0 + syslog_job: 0 + syslog_knl: 0 + syslog_lib: 0 + syslog_mgr: 0 + syslog_net: 0 + syslog_pts: 0 + syslog_tls: 0 + syslog_tnc: 0 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Configuring - Syslog - nothing changed + oxlorg.opnsense.ipsec_general: + syslog_log_name: false + syslog_log_level: true + syslog_app: 0 + syslog_asn: 0 + syslog_cfg: 0 + syslog_chd: 0 + syslog_dmn: 0 + syslog_enc: 0 + syslog_esp: 0 + syslog_ike: 0 + syslog_imc: 0 + syslog_imv: 0 + syslog_job: 0 + syslog_knl: 0 + syslog_lib: 0 + syslog_mgr: 0 + syslog_net: 0 + syslog_pts: 0 + syslog_tls: 0 + syslog_tnc: 0 + register: opn10 + failed_when: > + opn10.failed or + opn10.changed + when: not ansible_check_mode + + - name: Configuring - Attr + oxlorg.opnsense.ipsec_general: + attr_subnet: + - 172.16.0.0/24 + - 172.16.1.0/24 + attr_dns: + - 172.16.0.53 + - 172.16.1.53 + attr_wins: + - 172.16.0.42 + - 172.16.1.42 + unity_split_include: + - 172.16.0.0/24 + - 172.16.1.0/24 + unity_dns_search: opn.local + unity_dns_split: opn.local + unity_login_banner: | + Welcome to the Ansible Tests + unity_save_password: true + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + + - name: Configuring - Attr - nothing changed + oxlorg.opnsense.ipsec_general: + attr_subnet: + - 172.16.0.0/24 + - 172.16.1.0/24 + attr_dns: + - 172.16.0.53 + - 172.16.1.53 + attr_nbns: + - 172.16.0.42 + - 172.16.1.42 + unity_split_include: + - 172.16.0.0/24 + - 172.16.1.0/24 + unity_dns_search: opn.local + unity_dns_split: opn.local + unity_login_banner: | + Welcome to the Ansible Tests + unity_save_password: true + register: opn12 + failed_when: > + opn12.failed or + opn12.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ipsec_general: + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_manual_spd.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_manual_spd.yml new file mode 100644 index 0000000..b7cc943 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_manual_spd.yml @@ -0,0 +1,198 @@ +--- + +- name: Testing IPSec Manual SPD + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_manual_spd' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid request_id + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + request_id: 67890 + source: 192.168.1.0/24 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid connection_child + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + connection_child: 'DOESNOTEXIST' + source: 192.168.1.0/24 + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + request_id: 12345 + source: 192.168.1.0/24 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + request_id: 12345 + source: 192.168.1.0/24 + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + request_id: 1234 + source: 192.168.2.0/24 + destination: 10.10.10.0/24 + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Disabling 1 + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + request_id: 1234 + source: 192.168.2.0/24 + destination: 10.10.10.0/24 + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + request_id: 1234 + source: 192.168.2.0/24 + destination: 10.10.10.0/24 + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_1' + request_id: 1234 + source: 192.168.2.0/24 + destination: 10.10.10.0/24 + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Dependency on connection/child + when: not ansible_check_mode + block: + - name: Adding dummy connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_3_1' + + - name: Adding dummy child + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_2_1' + connection: 'ANSIBLE_TEST_3_1' + local_net: '192.168.1.0/28' + remote_net: '192.168.2.0/28' + + - name: Adding 2 + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_2' + source: 192.168.1.0/24 + connection_child: 'ANSIBLE_TEST_3_1 - ANSIBLE_TEST_2_1' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_2' + source: 192.168.1.0/24 + connection_child: 'ANSIBLE_TEST_3_1 - ANSIBLE_TEST_2_1' + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + + - name: Removing 2 + oxlorg.opnsense.ipsec_manual_spd: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Cleanup dummy child + oxlorg.opnsense.ipsec_child: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + + - name: Cleanup dummy connection + oxlorg.opnsense.ipsec_connection: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ipsec_manual_spd: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_pool.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_pool.yml new file mode 100644 index 0000000..c1c7652 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_pool.yml @@ -0,0 +1,175 @@ +--- + +- name: Testing IPSec pools/networks + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_pool' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid proposal (server-side) + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + network: 'INVALID-CIDR' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding 1 - failing because of invalid dns + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + network: '192.168.0.0/29' + dns: 'INVALID-IP' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + network: '192.168.0.0/29' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + network: '192.168.0.0/29' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + network: '192.168.1.0/28' + dns: '192.168.2.1' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Disabling 1 + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + network: '192.168.1.0/28' + dns: '192.168.2.1' + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed (w/ module alias) + oxlorg.opnsense.ipsec_network: + name: 'ANSIBLE_TEST_1_1' + network: '192.168.1.0/28' + dns: '192.168.2.1' + enabled: false + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + network: '192.168.1.0/28' + dns: '192.168.2.1' + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_1' + network: '192.168.1.0/28' + dns: '192.168.2.1' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_2' + network: '192.168.2.0/27' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_2' + network: '192.168.2.0/27' + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.ipsec_pool: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ipsec_pool: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_psk.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_psk.yml new file mode 100644 index 0000000..6a4f71a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_psk.yml @@ -0,0 +1,158 @@ +--- + +- name: Testing IPSec PSK + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_psk' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Cleanup + oxlorg.opnsense.ipsec_psk: + identity: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE@TEST1' + - 'ANSIBLE@TEST2' + - 'ANSIBLE@TEST2X' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing psk + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid identity (server-side) + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE_TEST_1_1' + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST1' + psk: 'my-super-secret' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST2' + identity_remote: 'ANSIBLE@TEST2B' + psk: 'LSfmuw3oiksfhnf3uhfwqhfwjuhnakfuwgfjsnhfnh' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST2' + identity_remote: 'ANSIBLE@TEST2B' + psk: 'LSfmuw3oiksfhnf3uhfwqhfwjuhnakfuwgfjsnhfnh' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Change - identity remote + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST2X' + identity_remote: 'ANSIBLE@TEST2B' + psk: 'LSfmuw3oiksfhnf3uhfwqhfwjuhnakfuwgfjsnhfnh_ooo' + match_fields: 'identity_remote' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Change - identity remote - nothing changed + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST2X' + identity_remote: 'ANSIBLE@TEST2B' + psk: 'LSfmuw3oiksfhnf3uhfwqhfwjuhnakfuwgfjsnhfnh_ooo' + match_fields: 'identity_remote' + register: opn10 + failed_when: > + opn10.failed or + opn10.changed + when: not ansible_check_mode + + - name: Change back - identity remote + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST2' + identity_remote: 'ANSIBLE@TEST2B' + psk: 'LSfmuw3oiksfhnf3uhfwqhfwjuhnakfuwgfjsnhfnh' + match_fields: 'identity_remote' + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.ipsec_psk: + identity: 'ANSIBLE@TEST2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ipsec_psk: + identity: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE@TEST1' + - 'ANSIBLE@TEST2' + - 'ANSIBLE@TEST2X' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_vti.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_vti.yml new file mode 100644 index 0000000..0353e71 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/ipsec_vti.yml @@ -0,0 +1,204 @@ +--- + +- name: Testing IPSec VTI + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'ipsec_vti' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing as not all addresses were provided + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + request_id: 100 + local_tunnel_address: '192.168.0.1/30' + remote_tunnel_address: '192.168.0.2/30' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing without request-id + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + local_address: '192.168.1.1' + remote_address: '192.168.1.2' + local_tunnel_address: '192.168.0.1' + remote_tunnel_address: '192.168.0.2' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + request_id: 100 + local_address: '192.168.1.1' + remote_address: '192.168.1.2' + local_tunnel_address: '192.168.0.1' + remote_tunnel_address: '192.168.0.2' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + request_id: 100 + local_address: '192.168.1.1' + remote_address: '192.168.1.2' + local_tunnel_address: '192.168.0.1' + remote_tunnel_address: '192.168.0.2' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + request_id: 110 + local_address: '192.168.10.1' + remote_address: '192.168.10.2' + local_tunnel_address: '192.168.0.5' + remote_tunnel_address: '192.168.0.6' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Disabling 1 + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + request_id: 110 + local_address: '192.168.10.1' + remote_address: '192.168.10.2' + local_tunnel_address: '192.168.0.5' + remote_tunnel_address: '192.168.0.6' + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + request_id: 110 + local_address: '192.168.10.1' + remote_address: '192.168.10.2' + local_tunnel_address: '192.168.0.5' + remote_tunnel_address: '192.168.0.6' + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_1' + request_id: 110 + local_address: '192.168.10.1' + remote_address: '192.168.10.2' + local_tunnel_address: '192.168.0.5' + remote_tunnel_address: '192.168.0.6' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_2' + request_id: 200 + local_address: '192.168.20.1' + remote_address: '192.168.20.2' + local_tunnel_address: '192.168.1.101' + remote_tunnel_address: '192.168.1.102' + local_tunnel_secondary_address: '192.168.2.101' + remote_tunnel_secondary_address: '192.168.2.102' + skip_firewall: true + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_2' + request_id: 200 + local_address: '192.168.20.1' + remote_address: '192.168.20.2' + local_tunnel_address: '192.168.1.101' + remote_tunnel_address: '192.168.1.102' + local_tunnel_secondary_address: '192.168.2.101' + remote_tunnel_secondary_address: '192.168.2.102' + skip_firewall: true + register: opn8 + failed_when: > + opn8.failed or + opn8.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.ipsec_vti: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.ipsec_vti: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/list.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/list.yml new file mode 100644 index 0000000..daad123 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/list.yml @@ -0,0 +1,110 @@ +--- + +- name: Testing List + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.list: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Querying config - basic modules + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: ['alias', 'rule', 'route', 'syslog', 'package', 'cron', 'source_nat'] + + - name: Querying config - Unbound + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: ['unbound_general', 'unbound_acl', 'unbound_host', 'unbound_forward', 'unbound_host_alias', 'unbound_dot'] + + - name: Querying config - Traffic shaper + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: ['shaper_pipe', 'shaper_queue', 'shaper_rule'] + + - name: Querying config - Monit + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: ['monit_service', 'monit_test', 'monit_alert'] + + - name: Querying config - BIND + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: ['bind_domain', 'bind_acl', 'bind_general', 'bind_blocklist', 'bind_record'] + + - name: Querying config - Interfaces + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: ['interface_vxlan', 'interface_vlan', 'interface_vip', 'interface_lagg'] + + - name: Querying config - WireGuard + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: ['wireguard_server', 'wireguard_peer'] + + - name: Querying config - FRR + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: [ + 'frr_ospf_general', 'frr_ospf3_general', 'frr_bfd_neighbor', 'frr_bgp_general', 'frr_bgp_neighbor', + 'frr_ospf3_interface', 'frr_ospf_interface', 'frr_ospf_network', 'frr_rip', 'frr_bgp_prefix_list', + 'frr_bgp_community_list', 'frr_bgp_as_path', 'frr_bgp_route_map', 'frr_ospf_prefix_list', 'frr_ospf_route_map', + ] + + - name: Querying config - WebProxy + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: [ + 'webproxy_general', 'webproxy_cache', 'webproxy_parent', 'webproxy_traffic', 'webproxy_remote_acl', + 'webproxy_pac_proxy', 'webproxy_pac_match', 'webproxy_pac_rule', 'webproxy_forward', 'webproxy_acl', + 'webproxy_icap', 'webproxy_auth', + ] + + - name: Querying config - IPSec + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: [ + 'ipsec_cert', 'ipsec_connection', 'ipsec_child', 'ipsec_pool', + 'ipsec_auth_local', 'ipsec_auth_remote', 'ipsec_vti', + ] + + - name: Querying config - IDS + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: [ + 'ids_general', 'ids_policy', 'ids_rule', 'ids_ruleset', 'ids_user_rule', + 'ids_policy_rule', + ] + + - name: Querying config - OpenVPN + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: [ + 'openvpn_instance', 'openvpn_static_key', 'openvpn_client_override', + ] + + - name: Querying config - DHCP + oxlorg.opnsense.list: + target: "{{ item }}" + when: not ansible_check_mode + loop: [ + 'dhcrelay_destination', 'dhcrelay_relay', 'dhcp_reservation', + ] diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_alert.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_alert.yml new file mode 100644 index 0000000..ec88d65 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_alert.yml @@ -0,0 +1,146 @@ +--- + +- name: Testing Monit alert + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'monit_alert' + + vars: + default_alert_count: 1 + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != default_alert_count + + - name: Removing - does not exist + oxlorg.opnsense.monit_alert: + recipient: 'alert@monit.opnsense.test.oxlorg.net' + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of invalid reminder + oxlorg.opnsense.monit_alert: + recipient: 'alert@monit.opnsense.test.oxlorg.net@INVALID' + reminder: 90000 + register: opn11 + failed_when: not opn11.failed + + - name: Adding 1 - failing because of invalid recipient + oxlorg.opnsense.monit_alert: + recipient: 'alert@monit.opnsense.test.oxlorg.net@INVALID' + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 + oxlorg.opnsense.monit_alert: + recipient: 'alert@monit.opnsense.test.oxlorg.net' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Disabling 1 + oxlorg.opnsense.monit_alert: + recipient: 'alert@monit.opnsense.test.oxlorg.net' + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.monit_alert: + recipient: 'alert@monit.opnsense.test.oxlorg.net' + enabled: false + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.monit_alert: + recipient: 'alert@monit.opnsense.test.oxlorg.net' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.monit_alert: + recipient: 'alert2@monit.opnsense.test.oxlorg.net' + not_on: true + events: ['timestamp'] + description: 'test1' + reminder: 500 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.monit_alert: + recipient: 'alert2@monit.opnsense.test.oxlorg.net' + not_on: true + events: ['timestamp'] + description: 'test1' + reminder: 500 + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.monit_alert: + recipient: 'alert2@monit.opnsense.test.oxlorg.net' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != (default_alert_count + 1) + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.monit_alert: + recipient: "{{ item }}" + state: 'absent' + loop: + - 'alert@monit.opnsense.test.oxlorg.net' + - 'alert2@monit.opnsense.test.oxlorg.net' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != default_alert_count + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_service.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_service.yml new file mode 100644 index 0000000..abf12cb --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_service.yml @@ -0,0 +1,220 @@ +--- + +- name: Testing Monit service + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'monit_service' + + vars: + default_svc_count: 4 + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != default_svc_count + + - name: Removing - does not exist + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of unsupported type + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_1' + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 - failing because of missing type-arguments + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_1' + type: "{{ item }}" + register: opn11 + failed_when: not opn11.failed + loop: + - 'network' # address or interface + - 'host' # address + + - name: Adding 1 - failing because of invalid address-ip + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_1' + type: 'network' + address: '192.168-INVALID-IP' + register: opn14 + failed_when: not opn14.failed + + - name: Adding 1 + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_1' + type: 'custom' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Disabling 1 + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_1' + type: 'custom' + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_1' + type: 'custom' + enabled: false + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_1' + type: 'custom' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_2' + type: 'network' + address: '192.168.0.1' + description: 'test1' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_2' + type: 'network' + address: '192.168.0.1' + description: 'test1' + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != (default_svc_count + 1) + when: not ansible_check_mode + + - name: Adding dummy test + oxlorg.opnsense.monit_test: + name: 'ANSIBLE_TEST_2_1' + condition: 'memory usage is greater than 90%' + type: 'SystemResource' + when: not ansible_check_mode + + - name: Adding 3 - linking test + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_3' + type: 'system' + tests: ['ANSIBLE_TEST_2_1'] + depends: ['ANSIBLE_TEST_1_1'] + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + when: not ansible_check_mode + + - name: Changing 3 - nothing changed + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_3' + type: 'system' + tests: ['ANSIBLE_TEST_2_1'] + depends: ['ANSIBLE_TEST_1_1'] + register: opn16 + failed_when: > + opn16.failed or + opn16.changed + when: not ansible_check_mode + + - name: Adding 4 - linking dependency + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_4' + type: 'custom' + depends: ['ANSIBLE_TEST_1_1'] + register: opn17 + failed_when: > + opn17.failed or + not opn17.changed + when: not ansible_check_mode + + - name: Changing 4 - nothing changed + oxlorg.opnsense.monit_service: + name: 'ANSIBLE_TEST_1_4' + type: 'custom' + depends: ['ANSIBLE_TEST_1_1'] + register: opn18 + failed_when: > + opn18.failed or + opn18.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.monit_service: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + when: not ansible_check_mode + + - name: Cleanup test + oxlorg.opnsense.monit_test: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != default_svc_count + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_test.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_test.yml new file mode 100644 index 0000000..ef8e799 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/monit_test.yml @@ -0,0 +1,111 @@ +--- + +- name: Testing Monit test + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'monit_test' + + vars: + default_test_count: 11 + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != default_test_count + + - name: Removing - does not exist + oxlorg.opnsense.monit_test: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of invalid type + oxlorg.opnsense.monit_test: + name: 'ANSIBLE_TEST_1_1' + type: '!INVALID!' + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 + oxlorg.opnsense.monit_test: + name: 'ANSIBLE_TEST_1_1' + condition: 'failed host 127.0.0.1 port 22 protocol ssh' + type: 'Connection' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Adding 2 + oxlorg.opnsense.monit_test: + name: 'ANSIBLE_TEST_1_2' + condition: 'memory usage is greater than 90%' + type: 'SystemResource' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.monit_test: + name: 'ANSIBLE_TEST_1_2' + condition: 'memory usage is greater than 90%' + type: 'SystemResource' + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.monit_test: + name: 'ANSIBLE_TEST_1_2' + condition: 'memory usage is greater than 90%' + type: 'SystemResource' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != (default_test_count + 1) + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.monit_test: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != default_test_count + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nat_one_to_one.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nat_one_to_one.yml new file mode 100644 index 0000000..9a47789 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nat_one_to_one.yml @@ -0,0 +1,165 @@ +--- + +- name: Testing One to One (BI)NAT + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + # match_fields: ['description'] + + oxlorg.opnsense.list: + target: 'nat_one_to_one' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.one_to_one: + description: 'DOES-NOT-EXIST' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid value + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.4' + source_net: '192.168.0.1' + description: 'ANSIBLE_TEST_1_1' + type: 'INVALID-TYPE' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.4' + source_net: '192.168.0.1' + description: 'ANSIBLE_TEST_1_1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.4' + source_net: '192.168.0.2' + description: 'ANSIBLE_TEST_1_1' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.4' + source_net: '192.168.0.2' + description: 'ANSIBLE_TEST_1_1' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.4' + source_net: '192.168.0.2' + description: 'ANSIBLE_TEST_1_1' + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.4' + source_net: '192.168.0.2' + description: 'ANSIBLE_TEST_1_1' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.5' + source_net: '192.168.0.3' + description: 'ANSIBLE_TEST_1_2' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.5' + source_net: '192.168.0.3' + description: 'ANSIBLE_TEST_1_2' + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.one_to_one: + interface: 'lan' + external: '1.2.3.5' + source_net: '192.168.0.3' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.one_to_one: + description: '{{ item }}' + match_fields: 'description' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nat_source.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nat_source.yml new file mode 100644 index 0000000..bb6d27b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nat_source.yml @@ -0,0 +1,180 @@ +--- + +- name: Testing source-nat + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.source_nat: + match_fields: ['description'] + + oxlorg.opnsense.list: + target: 'source_nat' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid alias (server-side) + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_1' + interface: 'lan' + destination: 'DoesNotExist' + target: '192.168.0.2' + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_1' + interface: 'lan' + destination: '192.168.0.1' + target: '192.168.0.2' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_1' + interface: 'lan' + destination: '192.168.0.1' + protocol: 'TCP' + target: '192.168.0.5' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_1' + interface: 'lan' + destination: '192.168.0.1' + protocol: 'TCP' + target: '192.168.0.5' + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed (w/ module alias) + oxlorg.opnsense.snat: + description: 'ANSIBLE_TEST_1_1' + interface: 'lan' + destination: '192.168.0.1' + protocol: 'TCP' + target: '192.168.0.5' + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_1' + interface: 'lan' + destination: '192.168.0.1' + protocol: 'TCP' + target: '192.168.0.5' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_2' + no_nat: true + interface: 'lan' + source_invert: true + source: 'bogons' + destination: '192.168.2.1' + destination_port: 51820 + protocol: 'UDP' + target: '192.168.3.1' + target_port: 51822 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_2' + no_nat: true + interface: 'lan' + source_invert: true + source: 'bogons' + destination: '192.168.2.1' + destination_port: 51820 + protocol: 'UDP' + target: '192.168.3.1' + target_port: 51822 + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.source_nat: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.source_nat: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/neighbor.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/neighbor.yml new file mode 100644 index 0000000..197518b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/neighbor.yml @@ -0,0 +1,125 @@ +--- + +- name: Testing Neighbor + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'neighbor' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.neighbor: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid ethernet_address + oxlorg.opnsense.neighbor: + description: 'ANSIBLE_TEST_1_1' + ethernet_address: 'INVALID' + ip_address: 192.168.1.1 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of invalid ip_address + oxlorg.opnsense.neighbor: + description: 'ANSIBLE_TEST_1_1' + ethernet_address: '00:11:22:33:44:55' + ip_address: 192.168.1.300 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.neighbor: + description: 'ANSIBLE_TEST_1_1' + ethernet_address: '00:11:22:33:44:55' + ip_address: 192.168.255.254 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.neighbor: + description: 'ANSIBLE_TEST_1_1' + ethernet_address: '00:11:22:33:44:55' + ip_address: 192.168.255.253 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Adding 2 + oxlorg.opnsense.neighbor: + description: 'ANSIBLE_TEST_1_2' + ethernet_address: '11:22:33:44:55:66' + ip_address: 192.168.255.252 + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.neighbor: + description: 'ANSIBLE_TEST_1_2' + ethernet_address: '11:22:33:44:55:66' + ip_address: 192.168.255.252 + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.neighbor: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn6 + failed_when: > + 'data' not in opn6 or + opn6.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.neighbor: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nginx_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nginx_general.yml new file mode 100644 index 0000000..ef766f4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nginx_general.yml @@ -0,0 +1,54 @@ +--- + +- name: Testing Nginx general settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Enabling - should work + oxlorg.opnsense.nginx_general: + enabled: true + register: opn_enb1 + failed_when: > + opn_enb1.failed + + - name: Re-enable - should work and do nothing + oxlorg.opnsense.nginx_general: + enabled: true + register: opn_enb2 + failed_when: > + opn_enb2.failed or opn_enb2.changed + when: not ansible_check_mode + + - name: Re-enable with new ban_ttl - should work and do something + oxlorg.opnsense.nginx_general: + enabled: true + ban_ttl: 10 + register: opn_enb3 + failed_when: > + opn_enb3.failed or not opn_enb3.changed + when: not ansible_check_mode + + - name: Disabling - should work and do something + oxlorg.opnsense.nginx_general: + enabled: false + register: opn_dsb1 + failed_when: > + opn_dsb1.failed or not opn_dsb1.changed + when: not ansible_check_mode + + - name: Re-disable - should work and do nothing + oxlorg.opnsense.nginx_general: + enabled: false + register: opn_dsb2 + failed_when: > + opn_dsb2.failed or opn_dsb2.changed + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nginx_upstream_server.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nginx_upstream_server.yml new file mode 100644 index 0000000..0cab7c5 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/nginx_upstream_server.yml @@ -0,0 +1,79 @@ +--- + +- name: Testing Nginx upstream server + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'nginx_upstream_server' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.nginx_upstream_server: + name: 'ANSIBLE@TEST1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing server + oxlorg.opnsense.nginx_upstream_server: + name: 'ANSIBLE@TEST1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of missing port + oxlorg.opnsense.nginx_upstream_server: + name: 'ANSIBLE@TEST1' + server: 192.168.1.1 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of missing priority + oxlorg.opnsense.nginx_upstream_server: + name: 'ANSIBLE@TEST1' + server: 192.168.1.1 + port: 80 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - should succeed + oxlorg.opnsense.nginx_upstream_server: + name: 'ANSIBLE@TEST1' + server: 192.168.1.1 + port: 80 + priority: 1 + + - name: Adding 1 - should succeed and not change + oxlorg.opnsense.nginx_upstream_server: + name: 'ANSIBLE@TEST1' + server: 192.168.1.1 + port: 80 + priority: 1 + register: ng1 + failed_when: > + ng1.failed or + ng1.changed + when: not ansible_check_mode + + - name: Remove 1 - should succeed + oxlorg.opnsense.nginx_upstream_server: + name: 'ANSIBLE@TEST1' + state: 'absent' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_client.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_client.yml new file mode 100644 index 0000000..4b93143 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_client.yml @@ -0,0 +1,247 @@ +--- + +- name: Testing OpenVPN Client + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'openvpn_instance' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because no remote was provided + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_1' + protocol: 'udp' + mode: 'tun' + certificate: 'OpenVPN Client' + ca: 'OpenVPN' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because no ca/cert was provided + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_1' + remote: 'openvpn.test.oxlorg.net:20000' + protocol: 'udp' + mode: 'tun' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_1' + remote: 'openvpn.test.oxlorg.net:20000' + protocol: 'udp' + mode: 'tun' + network_remote: ['192.168.77.128/27'] + ca: 'OpenVPN' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_1' + remote: 'openvpn.test.oxlorg.net:20000' + protocol: 'udp' + mode: 'tun' + network_remote: ['192.168.77.128/27', '192.168.89.64/27'] + log_level: 5 + ca: 'OpenVPN' + certificate: 'OpenVPN Client' + mtu: 1400 + mss_fix: true + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_1' + remote: 'openvpn.test.oxlorg.net:20000' + protocol: 'udp' + mode: 'tun' + network_remote: ['192.168.77.128/27', '192.168.89.64/27'] + log_level: 5 + ca: 'OpenVPN' + certificate: 'OpenVPN Client' + mtu: 1400 + mss_fix: true + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_1' + remote: 'openvpn.test.oxlorg.net:20000' + protocol: 'udp' + mode: 'tun' + network_remote: ['192.168.77.128/27', '192.168.89.64/27'] + log_level: 5 + ca: 'OpenVPN' + certificate: 'OpenVPN Client' + mtu: 1400 + mss_fix: true + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_1' + remote: 'openvpn.test.oxlorg.net:20000' + protocol: 'udp' + mode: 'tun' + network_remote: ['192.168.77.128/27', '192.168.89.64/27'] + log_level: 5 + ca: 'OpenVPN' + certificate: 'OpenVPN Client' + mtu: 1400 + mss_fix: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_2' + remote: 'openvpn.test.oxlorg.net:24000' + protocol: 'tcp' + mode: 'tap' + network_local: '192.168.67.0/29' + network_remote: '192.168.67.128/29' + ca: 'OpenVPN' + certificate: 'OpenVPN Client' + mtu: 1420 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_2' + remote: 'openvpn.test.oxlorg.net:24000' + protocol: 'tcp' + mode: 'tap' + network_local: '192.168.67.0/29' + network_remote: '192.168.67.128/29' + ca: 'OpenVPN' + certificate: 'OpenVPN Client' + mtu: 1420 + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.openvpn_client: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_post1 + failed_when: > + 'data' not in opn_post1 or + opn_post1.data | length != 0 + +- name: Testing OpenVPN Client with Static Key + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding dummy key + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_2_1' + when: not ansible_check_mode + + - name: Adding 1 with link to static-key + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_2_1' + remote: 'openvpn.test.oxlorg.net' + ca: 'OpenVPN' + key: 'ANSIBLE_TEST_2_1' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.openvpn_client: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup key + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_client_override.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_client_override.yml new file mode 100644 index 0000000..cd8016a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_client_override.yml @@ -0,0 +1,183 @@ +--- + +- name: Testing OpenVPN Client-Overwrite + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'openvpn_client_override' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding Dummy Servers + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_3_1' + server: '192.168.77.0/29' + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because no servers were provided + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_1' + servers: 'ANSIBLE_TEST_3_1' + redirect_gateway: '!ipv4' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_1' + servers: 'ANSIBLE_TEST_3_1' + block: true + network_tunnel_ip4: '192.168.77.3/29' + network_local: ['192.168.78.128/27'] + domain: 'test.vpn' + dns_servers: ['1.1.1.1', '8.8.8.8'] + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_1' + servers: 'ANSIBLE_TEST_3_1' + block: true + network_tunnel_ip4: '192.168.77.3/29' + network_local: ['192.168.78.128/27'] + domain: 'test.vpn' + dns_servers: ['1.1.1.1', '8.8.8.8'] + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_1' + servers: 'ANSIBLE_TEST_3_1' + block: true + network_tunnel_ip4: '192.168.77.3/29' + network_local: ['192.168.78.128/27'] + domain: 'test.vpn' + dns_servers: ['1.1.1.1', '8.8.8.8'] + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_1' + servers: 'ANSIBLE_TEST_3_1' + block: true + network_tunnel_ip4: '192.168.77.3/29' + network_local: ['192.168.78.128/27'] + domain: 'test.vpn' + dns_servers: ['1.1.1.1', '8.8.8.8'] + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_2' + servers: 'ANSIBLE_TEST_3_1' + ntp_servers: '192.168.1.1' + register_dns: true + domain_list: ['internal', 'test.local'] + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 - nothing changed + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_2' + servers: 'ANSIBLE_TEST_3_1' + ntp_servers: '192.168.1.1' + register_dns: true + domain_list: ['internal', 'test.local'] + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.openvpn_client_override: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.openvpn_client_override: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Cleanup Server + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_post1 + failed_when: > + 'data' not in opn_post1 or + opn_post1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_server.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_server.yml new file mode 100644 index 0000000..78b3437 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_server.yml @@ -0,0 +1,296 @@ +--- + +- name: Testing OpenVPN Server + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'openvpn_instance' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because no client-net was provided + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_1' + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because no ca/cert was provided + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_1' + server: '192.168.77.0/29' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_1' + port: 20000 + protocol: 'udp' + mode: 'tun' + server: '192.168.77.0/29' + network_local: ['192.168.78.128/27'] + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_1' + port: 20000 + protocol: 'udp' + mode: 'tun' + server: '192.168.77.0/29' + network_local: ['192.168.78.128/27'] + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + cert_depth: 1 + data_ciphers: ['AES-256-GCM', 'CHACHA20-POLY1305'] + max_connections: 100 + user_as_cn: true + user_cn_strict: true + push_options: ['block-outside-dns', 'register-dns'] + mtu: 1400 + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Disabling 1 + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_1' + port: 20000 + protocol: 'udp' + mode: 'tun' + server: '192.168.77.0/29' + network_local: ['192.168.78.128/27'] + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + cert_depth: 1 + data_ciphers: ['AES-256-GCM', 'CHACHA20-POLY1305'] + max_connections: 100 + user_as_cn: true + user_cn_strict: true + push_options: ['block-outside-dns', 'register-dns'] + mtu: 1400 + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_1' + port: 20000 + protocol: 'udp' + mode: 'tun' + server: '192.168.77.0/29' + network_local: ['192.168.78.128/27'] + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + cert_depth: 1 + data_ciphers: ['AES-256-GCM', 'CHACHA20-POLY1305'] + max_connections: 100 + user_as_cn: true + user_cn_strict: true + push_options: ['block-outside-dns', 'register-dns'] + mtu: 1400 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_1' + port: 20000 + protocol: 'udp' + mode: 'tun' + server: '192.168.77.0/29' + network_local: ['192.168.78.128/27'] + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + cert_depth: 1 + data_ciphers: ['AES-256-GCM', 'CHACHA20-POLY1305'] + max_connections: 100 + user_as_cn: true + user_cn_strict: true + push_options: ['block-outside-dns', 'register-dns'] + mtu: 1400 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_2' + port: 20010 + port_share: 127.0.0.1:443 + protocol: 'tcp4' + mode: 'tap' + server: '192.168.77.128/29' + network_local: ['192.168.78.0/27'] + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + verify_remote_certificate: true + data_cipher_fallback: 'CHACHA20-POLY1305' + max_connections: 10 + user_as_cn: true + auth_token_time: 3600 + auth_token_renewal: 5 + require_client_provisioning: true + mtu: 1420 + dns_servers: ['1.1.1.1', '8.8.8.8'] + redirect_gateway: + - def1 + - block-local + persist_address_pool: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_2' + port: 20010 + port_share: 127.0.0.1:443 + protocol: 'tcp4' + mode: 'tap' + server: '192.168.77.128/29' + network_local: ['192.168.78.0/27'] + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + verify_remote_certificate: true + data_cipher_fallback: 'CHACHA20-POLY1305' + max_connections: 10 + user_as_cn: true + auth_token_time: 3600 + auth_token_renewal: 5 + require_client_provisioning: true + mtu: 1420 + dns_servers: ['1.1.1.1', '8.8.8.8'] + redirect_gateway: + - def1 + - block-local + persist_address_pool: true + when: not ansible_check_mode + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + + - name: Removing 2 + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.openvpn_server: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_post1 + failed_when: > + 'data' not in opn_post1 or + opn_post1.data | length != 0 + +- name: Testing OpenVPN Server with Static Key + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding dummy key + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_3_1' + when: not ansible_check_mode + + - name: Adding 1 with link to static-key + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_2_1' + port: 20000 + protocol: 'udp' + mode: 'tun' + server: '192.168.77.0/29' + network_local: ['192.168.78.128/27'] + ca: 'OpenVPN' + certificate: 'OpenVPN Server' + key: 'ANSIBLE_TEST_3_1' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.openvpn_server: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup key + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_static_key.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_static_key.yml new file mode 100644 index 0000000..0e91031 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_static_key.yml @@ -0,0 +1,122 @@ +--- + +- name: Testing OpenVPN Static-Keys + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'openvpn_static_key' + + vars: + test_key: '#\n# 2048 bit OpenVPN static key\n#\n-----BEGIN OpenVPN Static key V1-----\n + c07e43dc02829f88184b4fb74243e4ac\nb1d24d1d1a74cd21df8ac64a527915ae\n9c736c0c219eb33774e40e61f6f660c8\n + daf44730850fae665f5f609a71e99f3c\n8a636b16dff7434ce3b7f9aca896287b\nd6c62d2f6d7db4e9cfcfe0f101cc6474\n + 0c98246fbcd203891a0343777c7551c7\naa2ba1e6a6ab4fcf593a894d4da8f180\nd44645b5a658e17f5d48408a020430c3\n + 5b768f413a2ec69ead015750cacb53d7\n64a19bce04b29f11d3ca7560a99958b6\n9203f493fd7e740b5a5a3d1afe1b4185\n + 50043805c5bac513baf2306e42c1c1f8\n0fd16661536a3ee72ffbd1d2d1b1f6c0\n9683064c9bc044ee0357f4b94f5687ed\n + 67cb013625cfb9b113ecff16674d63e6\n-----END OpenVPN Static key V1-----' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_1_1' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_1_1' + key: "{{ test_key }}" + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_1_1' + key: "{{ test_key }}" + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_1_2' + mode: 'auth' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_1_2' + mode: 'auth' + register: opn6 + failed_when: > + opn6.failed or + opn6.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.openvpn_static_key: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.openvpn_static_key: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_post1 + failed_when: > + 'data' not in opn_post1 or + opn_post1.data | length != 0 diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_status.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_status.yml new file mode 100644 index 0000000..6836813 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/openvpn_status.yml @@ -0,0 +1,26 @@ +--- + +- name: Testing OpenVPN Status + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Querying OpenVPN Sessions + oxlorg.opnsense.openvpn_status: + target: 'sessions' + register: opn1 + failed_when: "'data' not in opn1" + + - name: Querying OpenVPN Routes + oxlorg.opnsense.openvpn_status: + target: 'routes' + register: opn2 + failed_when: "'data' not in opn2" diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/package.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/package.yml new file mode 100644 index 0000000..5408c58 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/package.yml @@ -0,0 +1,136 @@ +--- + +- name: Testing Packages + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.package: + timeout: 120 + + oxlorg.opnsense.list: + target: 'package' + + vars: + test_app1: 'os-hw-probe' + test_app2: 'os-dmidecode' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: "'data' not in opn10" + + - name: Installing - exists + oxlorg.opnsense.package: + name: 'os-squid' + action: 'install' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Removing - does not exist + oxlorg.opnsense.package: + name: "{{ test_app1 }}" + action: 'remove' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + + - name: Installing + oxlorg.opnsense.package: + name: "{{ test_app1 }}" + action: 'install' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Installing multiple + oxlorg.opnsense.package: + name: ["{{ test_app1 }}", "{{ test_app2 }}"] + action: 'install' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + + - name: Waiting for backend-data to refresh + ansible.builtin.pause: + seconds: 5 + when: not ansible_check_mode + + - name: Checking if installed + oxlorg.opnsense.list: + register: opn12 + failed_when: > + opn12.failed or + test_app1 not in opn12.data | map(attribute='name') or + test_app2 not in opn12.data | map(attribute='name') + when: not ansible_check_mode + + - name: Locking + oxlorg.opnsense.package: + name: "{{ test_app1 }}" + action: 'lock' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Removing - locked + oxlorg.opnsense.package: + name: "{{ test_app1 }}" + action: 'remove' + register: opn5 + failed_when: not opn5.failed + when: not ansible_check_mode + + - name: Unlocking + oxlorg.opnsense.package: + name: "{{ test_app1 }}" + action: 'unlock' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Reinstall + oxlorg.opnsense.package: + name: "{{ test_app1 }}" + action: 'reinstall' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Removing multiple + oxlorg.opnsense.package: + name: ["{{ test_app1 }}", "{{ test_app2 }}"] + action: 'remove' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Checking if removed + oxlorg.opnsense.list: + register: opn11 + failed_when: > + opn11.failed or + test_app1 in opn11.data | map(attribute='name') or + test_app2 in opn11.data | map(attribute='name') + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_address.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_address.yml new file mode 100644 index 0000000..f91900e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_address.yml @@ -0,0 +1,148 @@ +--- + +- name: Testing Postfix Address + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_address' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.postfix_address: + address: 'ANSIBLE_TEST_99_9' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing to value + oxlorg.opnsense.postfix_address: + address: alice@example.com + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.postfix_address: + address: alice@example.com + to: bob@example.com + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.postfix_address: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.postfix_address: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.postfix_address: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.postfix_address: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.postfix_address: + address: bob@example.com + to: carol@example.com + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.postfix_address: + address: bob@example.com + to: carol@example.com + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.postfix_address: + address: bob@example.com + state: absent + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_address: + address: alice@example.com + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_domain.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_domain.yml new file mode 100644 index 0000000..eb76309 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_domain.yml @@ -0,0 +1,132 @@ +--- + +- name: Testing Postfix Domain + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_domain' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.postfix_domain: + domainname: 'ANSIBLE_TEST_99_9' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 + oxlorg.opnsense.postfix_domain: + domainname: example.com + destination: 192.168.100.2 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.postfix_domain: + domainname: example.com + destination: 192.168.100.3 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.postfix_domain: + domainname: example.com + destination: 192.168.100.3 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.postfix_domain: + domainname: example.com + destination: 192.168.100.3 + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.postfix_domain: + domainname: example.com + destination: 192.168.100.3 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.postfix_domain: + domainname: example.net + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.postfix_domain: + domainname: example.net + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.postfix_domain: + domainname: example.net + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_domain: + domainname: example.com + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_general.yml new file mode 100644 index 0000000..9348616 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_general.yml @@ -0,0 +1,105 @@ +--- + +- name: Testing Postfix Setting + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid inet_port + oxlorg.opnsense.postfix_general: + inet_port: 999999 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid ip_version + oxlorg.opnsense.postfix_general: + ip_version: ipv5 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring + oxlorg.opnsense.postfix_general: + myhostname: mx.example.com + mydomain: example.com + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.postfix_general: + myhostname: mx.example.com + mydomain: example.com + inet_port: 26 + ip_version: ipv4 + mynetworks: + - '127.0.0.0/8' + - '[::1]/128' + - '[:ffff:127.0.0.0]/104' + - '192.168.0.0/16' + masquerade_domains: + - example.com + - example.net + tls_server_compatibility: modern + tls_client_compatibility: modern + tlswrappermode: true + smtpclient_security: encrypt + relayhost: relay.example.com + smtpauth_enabled: true + smtpauth_user: postfix + smtpauth_password: SECRET + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.postfix_general: + myhostname: mx.example.com + mydomain: example.com + inet_port: 26 + ip_version: ipv4 + mynetworks: + - '127.0.0.0/8' + - '[::1]/128' + - '[:ffff:127.0.0.0]/104' + - '192.168.0.0/16' + masquerade_domains: + - example.com + - example.net + tls_server_compatibility: modern + tls_client_compatibility: modern + tlswrappermode: true + smtpclient_security: encrypt + relayhost: relay.example.com + smtpauth_enabled: true + smtpauth_user: postfix + smtpauth_password: SECRET + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_general: + enabled: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_headercheck.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_headercheck.yml new file mode 100644 index 0000000..5541e6b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_headercheck.yml @@ -0,0 +1,134 @@ +--- + +- name: Testing Postfix Headercheck + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_headercheck' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.postfix_headercheck: + expression: 'ANSIBLE_TEST_99_9' + filter: WHILE_RECEIVING + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing filter + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: WHILE_RECEIVING + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Disabling 1 + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: WHILE_RECEIVING + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: WHILE_RECEIVING + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: WHILE_RECEIVING + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: WHILE_DELIVERING + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: WHILE_DELIVERING + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: WHILE_DELIVERING + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_headercheck: + expression: '/^\s*User-Agent/ IGNORE' + filter: WHILE_RECEIVING + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_recipient.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_recipient.yml new file mode 100644 index 0000000..6a5c20a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_recipient.yml @@ -0,0 +1,140 @@ +--- + +- name: Testing Postfix Recipient + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_recipient' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.postfix_recipient: + address: 'ANSIBLE_TEST_99_9' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing action + oxlorg.opnsense.postfix_recipient: + address: alice@example.com + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.postfix_recipient: + address: alice@example.com + action: REJECT + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.postfix_recipient: + address: alice@example.com + action: OK + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.postfix_recipient: + address: alice@example.com + action: OK + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.postfix_recipient: + address: alice@example.com + action: OK + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.postfix_recipient: + address: alice@example.com + action: OK + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.postfix_recipient: + address: bob@example.com + action: REJECT + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.postfix_recipient: + address: bob@example.com + action: REJECT + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.postfix_recipient: + address: bob@example.com + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_recipient: + address: alice@example.com + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_recipientbcc.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_recipientbcc.yml new file mode 100644 index 0000000..f846970 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_recipientbcc.yml @@ -0,0 +1,148 @@ +--- + +- name: Testing Postfix Recipient BCC + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_recipientbcc' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.postfix_recipientbcc: + address: 'ANSIBLE_TEST_99_9' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing to value + oxlorg.opnsense.postfix_recipientbcc: + address: alice@example.com + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.postfix_recipientbcc: + address: alice@example.com + to: bob@example.com + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.postfix_recipientbcc: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.postfix_recipientbcc: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.postfix_recipientbcc: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.postfix_recipientbcc: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.postfix_recipientbcc: + address: bob@example.com + to: carol@example.com + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.postfix_recipientbcc: + address: bob@example.com + to: carol@example.com + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.postfix_recipientbcc: + address: bob@example.com + state: absent + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_recipientbcc: + address: alice@example.com + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_sender.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_sender.yml new file mode 100644 index 0000000..9e875f6 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_sender.yml @@ -0,0 +1,140 @@ +--- + +- name: Testing Postfix Sender + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_sender' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.postfix_sender: + address: 'ANSIBLE_TEST_99_9' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing action + oxlorg.opnsense.postfix_sender: + address: alice@example.com + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.postfix_sender: + address: alice@example.com + action: REJECT + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.postfix_sender: + address: alice@example.com + action: OK + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.postfix_sender: + address: alice@example.com + action: OK + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.postfix_sender: + address: alice@example.com + action: OK + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.postfix_sender: + address: alice@example.com + action: OK + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.postfix_sender: + address: bob@example.com + action: REJECT + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.postfix_sender: + address: bob@example.com + action: REJECT + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.postfix_sender: + address: bob@example.com + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_sender: + address: alice@example.com + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_senderbcc.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_senderbcc.yml new file mode 100644 index 0000000..6af974e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_senderbcc.yml @@ -0,0 +1,148 @@ +--- + +- name: Testing Postfix Sender BCC + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_senderbcc' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.postfix_senderbcc: + address: 'ANSIBLE_TEST_99_9' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing to value + oxlorg.opnsense.postfix_senderbcc: + address: alice@example.com + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.postfix_senderbcc: + address: alice@example.com + to: bob@example.com + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.postfix_senderbcc: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.postfix_senderbcc: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.postfix_senderbcc: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.postfix_senderbcc: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.postfix_senderbcc: + address: bob@example.com + to: carol@example.com + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.postfix_senderbcc: + address: bob@example.com + to: carol@example.com + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.postfix_senderbcc: + address: bob@example.com + state: absent + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_senderbcc: + address: alice@example.com + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_sendercanonical.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_sendercanonical.yml new file mode 100644 index 0000000..02cc238 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/postfix_sendercanonical.yml @@ -0,0 +1,148 @@ +--- + +- name: Testing Postfix Sender Canonical + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'postfix_sendercanonical' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.postfix_sendercanonical: + address: 'ANSIBLE_TEST_99_9' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of missing to value + oxlorg.opnsense.postfix_sendercanonical: + address: alice@example.com + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.postfix_sendercanonical: + address: alice@example.com + to: bob@example.com + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.postfix_sendercanonical: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.postfix_sendercanonical: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.postfix_sendercanonical: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.postfix_sendercanonical: + address: alice@example.com + to: + - bob@example.com + - carol@example.com + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.postfix_sendercanonical: + address: bob@example.com + to: carol@example.com + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.postfix_sendercanonical: + address: bob@example.com + to: carol@example.com + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.postfix_sendercanonical: + address: bob@example.com + state: absent + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.postfix_sendercanonical: + address: alice@example.com + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/privilege.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/privilege.yml new file mode 100644 index 0000000..4e8c52a --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/privilege.yml @@ -0,0 +1,141 @@ +--- + +- name: Testing Access Privilege + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'privilege' + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length == 0 + when: false + + - name: Adding 1 - failing because unknown privilage + oxlorg.opnsense.privilege: + privilege: ANSIBLE_TEST_1_1 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding Dummy User + oxlorg.opnsense.user: + name: '{{ item }}' + loop: + - ANSIBLE_TEST_1_1 + - ANSIBLE_TEST_1_2 + + - name: Adding Dummy Group + oxlorg.opnsense.group: + name: '{{ item }}' + loop: + - ANSIBLE_TEST_1_1 + - ANSIBLE_TEST_1_2 + + - name: Granting 1 + oxlorg.opnsense.privilege: + privilege: user-config-readonly + user: ANSIBLE_TEST_1_1 + group: ANSIBLE_TEST_1_1 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Granting 1 - nothing changed + oxlorg.opnsense.privilege: + privilege: user-config-readonly + user: ANSIBLE_TEST_1_1 + group: ANSIBLE_TEST_1_1 + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + + - name: Revoking - does not exist + oxlorg.opnsense.privilege: + privilege: user-config-readonly + user: ANSIBLE_TEST_1_2 + group: ANSIBLE_TEST_1_2 + state: absent + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + + - name: Revoking 1 + oxlorg.opnsense.privilege: + privilege: user-config-readonly + user: ANSIBLE_TEST_1_1 + group: ANSIBLE_TEST_1_1 + state: absent + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Revoking 1 - nothing changed + oxlorg.opnsense.privilege: + privilege: user-config-readonly + user: ANSIBLE_TEST_1_1 + group: ANSIBLE_TEST_1_1 + state: absent + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + + - name: Granting 2 + oxlorg.opnsense.privilege: + privilege: user-config-readonly + user: + - ANSIBLE_TEST_1_1 + - ANSIBLE_TEST_1_2 + group: + - ANSIBLE_TEST_1_1 + state: pure + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Granting 2 - nothing changed + oxlorg.opnsense.privilege: + privilege: user-config-readonly + user: + - ANSIBLE_TEST_1_1 + - ANSIBLE_TEST_1_2 + group: + - ANSIBLE_TEST_1_1 + state: pure + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + + - name: Cleanup Dummy User + oxlorg.opnsense.user: + name: '{{ item }}' + state: 'absent' + loop: + - ANSIBLE_TEST_1_1 + - ANSIBLE_TEST_1_2 + diff: false + + - name: Adding Dummy Group + oxlorg.opnsense.group: + name: '{{ item }}' + state: 'absent' + loop: + - ANSIBLE_TEST_1_1 + - ANSIBLE_TEST_1_2 + diff: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/raw.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/raw.yml new file mode 100644 index 0000000..971dbcd --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/raw.yml @@ -0,0 +1,46 @@ +--- + +- name: Testing RAW + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Query interfaces + oxlorg.opnsense.raw: + url: 'interfaces/overview/interfacesInfo' + register: opn1 + failed_when: > + opn1.failed or + opn1.response.rows is undefined or + opn1.response.rows | length == 0 + + - name: Query interfaces 2 + oxlorg.opnsense.raw: + module: 'interfaces' + controller: 'overview' + command: 'interfacesInfo' + register: opn2 + failed_when: > + opn2.failed or + opn2.response.rows is undefined or + opn2.response.rows | length == 0 + + - name: Execute action + oxlorg.opnsense.raw: + module: 'syslog' + controller: 'service' + command: 'restart' + action: 'post' + register: opn3 + failed_when: > + opn3.failed or + (ansible_check_mode and opn3.changed) or + (not ansible_check_mode and not opn3.changed) diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/reload.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/reload.yml new file mode 100644 index 0000000..a2f6105 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/reload.yml @@ -0,0 +1,44 @@ +--- + +- name: Testing Reload + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.reload: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Reloading + oxlorg.opnsense.reload: + target: "{{ item }}" + timeout: 90 + when: not ansible_check_mode + loop: + - 'alias' + - 'rule' + - 'route' + - 'cron' # issue 88 + # - 'unbound' taking way too long for testing (90+ sec) + - 'syslog' + - 'ipsec' + - 'ipsec_legacy' + - 'shaper' + - 'monit' + - 'wireguard' + - 'interface_vlan' + - 'interface_vxlan' + - 'interface_vip' + - 'interface_lagg' + - 'frr' + - 'webproxy' + - 'bind' + - 'ids' + - 'openvpn' + - 'dhcrelay' + - 'dhcp' + - 'gateway' # issue 88 diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/route.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/route.yml new file mode 100644 index 0000000..890b72c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/route.yml @@ -0,0 +1,134 @@ +--- + +- name: Testing Routes + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.route: + match_fields: ['description'] + + oxlorg.opnsense.list: + target: 'route' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Removing - does not exist + oxlorg.opnsense.route: + description: 'ANSIBLE_TEST_1' + network: '4.4.4.4/32' + gateway: 'LAN_GW' + state: 'absent' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + + - name: Adding 1 + oxlorg.opnsense.route: + description: 'ANSIBLE_TEST_1' + network: '4.4.4.1/32' + gateway: 'LAN_GW' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Adding 2 + oxlorg.opnsense.route: + description: 'ANSIBLE_TEST_2' + network: '4.4.4.2/32' + gateway: 'TEST-GW' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.route: + description: 'ANSIBLE_TEST_2' + network: '4.4.4.2/32' + gateway: 'TEST-GW' + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Adding 2 - nothing changed with match-fields network & gateway + oxlorg.opnsense.route: + description: 'ANSIBLE_TEST_2' + network: '4.4.4.2/32' + gateway: 'TEST-GW' + match_fields: ['network', 'gateway'] + register: opn10 + failed_when: > + opn10.failed or + opn10.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.route: + description: 'ANSIBLE_TEST_1' + network: '4.4.4.1/32' + gateway: 'LAN_GW' + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.route: + description: 'ANSIBLE_TEST_1' + network: '4.4.4.1/32' + gateway: 'LAN_GW' + enabled: false + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.route: + description: 'ANSIBLE_TEST_1' + network: '4.4.4.1/32' + gateway: 'LAN_GW' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing rules + oxlorg.opnsense.list: + register: opn1 + failed_when: > + 'data' not in opn1 or + opn1.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.route: + description: "{{ item }}" + network: '4.4.4.1/32' + gateway: 'LAN_GW' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + + - name: Listing rules + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule.yml new file mode 100644 index 0000000..2786e07 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule.yml @@ -0,0 +1,736 @@ +--- + +- name: Testing Rules - basics + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.rule: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + match_fields: ['description'] + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Deleting - does not exist + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.1.0/24' + destination_port: 443 + protocol: 'TCP' + state: 'absent' + description: 'ANSIBLE_TEST_1_1' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Creating + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.1.0/24' + destination_port: 443 + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_1' + + - name: Updating - nothing changed + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.1.0/24' + destination_port: 443 + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_1' + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Updating + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.2.0/24' + destination_port: 443 + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_1' + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Updating more + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.2.0/24' + destination_port: 8443 + protocol: 'UDP' + description: 'ANSIBLE_TEST_1_1' + interface: ['lan'] # note: 'lo0' not accepted :( + log: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.2.0/24' + destination_port: 8443 + protocol: 'UDP' + description: 'ANSIBLE_TEST_1_1' + interface: ['lan'] # note: 'lo0' not accepted :( + log: false + enabled: false + register: opn69 + failed_when: > + opn69.failed or + not opn69.changed + when: not ansible_check_mode + + - name: Disabling - nothing changed + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.2.0/24' + destination_port: 8443 + protocol: 'UDP' + description: 'ANSIBLE_TEST_1_1' + interface: ['lan'] # note: 'lo0' not accepted :( + log: false + enabled: false + register: opn70 + failed_when: > + opn70.failed or + opn70.changed + when: not ansible_check_mode + + - name: Enabling + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.2.0/24' + destination_port: 8443 + protocol: 'UDP' + description: 'ANSIBLE_TEST_1_1' + interface: ['lan'] # note: 'lo0' not accepted :( + log: false + enabled: true + register: opn71 + failed_when: > + opn71.failed or + not opn71.changed + when: not ansible_check_mode + + - name: Creating IPv6 + oxlorg.opnsense.rule: + destination_port: 443 + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_10' + ip_protocol: 'inet6' + register: opn17 + failed_when: > + opn17.failed or + not opn17.changed + + - name: Updating IPv4 + oxlorg.opnsense.rule: + destination_port: 443 + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_10' + register: opn18 + failed_when: > + opn18.failed or + not opn18.changed + when: not ansible_check_mode + + - name: Triggering server-side validation error for interfaces + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.2.0/24' + destination_port: 443 + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_11' + interface: ['lan', 'does_not_exist'] + register: opn19 + failed_when: not opn19.failed + when: not ansible_check_mode + + - name: Triggering server-side validation error for ports + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.2.0/24' + destination_port: 'XYZ' + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_11' + register: opn20 + failed_when: not opn20.failed + when: not ansible_check_mode + + - name: Creating block rule + oxlorg.opnsense.rule: + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '10.0.0.0/8' + description: 'ANSIBLE_TEST_1_2' + action: 'block' + + - name: Test port-range + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.2.0/24' + destination_port: '50000-51000' + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_12' + + - name: Test self (this firewall) + oxlorg.opnsense.rule: + destination_net: '(self)' + destination_port: 69 + protocol: 'UDP' + description: 'ANSIBLE_TEST_1_13' + register: opn21 + failed_when: > + opn21.failed or + not opn21.changed + + - name: Test TCP/UDP inet46 + oxlorg.opnsense.rule: + destination_port: 69 + ip_protocol: 'inet46' + protocol: 'TCP/UDP' + description: 'ANSIBLE_TEST_1_14' + register: opn22 + failed_when: > + opn22.failed or + not opn22.changed + + - name: Test ICMP-type + oxlorg.opnsense.rule: + ip_protocol: 'inet46' + protocol: 'ICMP' + icmp_type: ['echoreq', 'echorep'] + description: 'ANSIBLE_TEST_1_15' + register: opn23 + failed_when: > + opn23.failed or + not opn23.changed + + - name: Test ICMP-type - Nothing changed + oxlorg.opnsense.rule: + ip_protocol: 'inet46' + protocol: 'ICMP' + icmp_type: ['echoreq', 'echorep'] + description: 'ANSIBLE_TEST_1_15' + register: opn24 + failed_when: > + opn24.failed or + opn24.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.rule: + description: "{{ item }}" + state: 'absent' + diff: false + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_10' + - 'ANSIBLE_TEST_1_11' + - 'ANSIBLE_TEST_1_12' + - 'ANSIBLE_TEST_1_13' + - 'ANSIBLE_TEST_1_14' + - 'ANSIBLE_TEST_1_15' + +- name: Testing Rules - aliases + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.rule: + match_fields: ['description'] + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding alias + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_3_1' + type: 'urltable' + content: 'https://www.spamhaus.org/drop/drop.txt' + + - name: Adding port alias + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_3_2' + type: 'port' + content: 80 + + - name: Adding rule using alias-destination + oxlorg.opnsense.rule: + destination_invert: true + destination_net: 'ANSIBLE_TEST_3_1' + description: 'ANSIBLE_TEST_1_3' + action: 'block' + + - name: Adding rule using alias-port + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + destination_net: '192.168.1.0/24' + destination_port: 'ANSIBLE_TEST_3_2' + protocol: 'TCP' + description: 'ANSIBLE_TEST_1_4' + + - name: Cleanup rules + oxlorg.opnsense.rule: + description: "{{ item }}" + state: 'absent' + diff: false + loop: + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + + - name: Cleanup aliases + oxlorg.opnsense.alias: + name: "{{ item }}" + state: 'absent' + diff: false + loop: + - 'ANSIBLE_TEST_3_1' + - 'ANSIBLE_TEST_3_2' + +- name: Testing Rules - listing + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'rule' + + oxlorg.opnsense.rule: + match_fields: ['description'] + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing rules (none) + oxlorg.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 0 + + - name: Creating dummy rule + oxlorg.opnsense.rule: + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '10.0.0.0/8' + description: 'ANSIBLE_TEST_1_5' + action: 'block' + when: not ansible_check_mode + + - name: Listing rules + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Modifying dummy rule 1 by uuid + oxlorg.opnsense.rule: + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '10.1.0.0/8' + description: 'ANSIBLE_TEST_1_5' + action: 'block' + match_fields: ['uuid'] + uuid: "{{ opn8.data[0]['uuid'] }}" + when: not ansible_check_mode + + - name: Creating dummy rule 2 + oxlorg.opnsense.rule: + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '10.0.0.0/8' + description: 'ANSIBLE_TEST_1_8' + enabled: false + action: 'block' + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.rule: + description: "{{ item }}" + state: 'absent' + diff: false + loop: + - 'ANSIBLE_TEST_1_5' + - 'ANSIBLE_TEST_1_8' + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn14 + failed_when: > + 'data' not in opn14 or + opn14.data | length != 0 + +- name: Testing Rules - matching + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'rule' + + oxlorg.opnsense.rule: + match_fields: ['source_net', 'description'] + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing rules - before + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + when: not ansible_check_mode + + - name: Creating rule 1 + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + description: 'ANSIBLE_TEST_1_6' + + destination_net: '192.168.1.0/24' + destination_port: 443 + protocol: 'TCP' + + - name: Modifying rule 1 + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + description: 'ANSIBLE_TEST_1_6' + + destination_net: '192.168.10.0/24' + destination_port: 443 + protocol: 'TCP' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Creating rule 2 + oxlorg.opnsense.rule: + source_net: '192.168.0.0/24' + description: 'ANSIBLE_TEST_1_7' + + destination_net: '192.168.1.0/24' + destination_port: 443 + protocol: 'TCP' + + - name: Creating rule 3 + oxlorg.opnsense.rule: + source_net: '192.168.1.0/24' + description: 'ANSIBLE_TEST_1_7' + + destination_net: '192.168.1.0/24' + destination_port: 443 + protocol: 'TCP' + + - name: Modifying rule 3 + oxlorg.opnsense.rule: + source_net: '192.168.1.0/24' + description: 'ANSIBLE_TEST_1_7' + + destination_net: '192.168.1.0/24' + destination_port: 443 + protocol: 'UDP' + when: not ansible_check_mode + + - name: Listing rules - after + oxlorg.opnsense.list: + register: opn11 + failed_when: > + 'data' not in opn11 or + opn11.data | length != 3 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.rule: + description: "{{ item }}" + state: 'absent' + match_fields: ['description'] + diff: false + loop: + - 'ANSIBLE_TEST_1_6' + - 'ANSIBLE_TEST_1_7' + - 'ANSIBLE_TEST_1_7' + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn15 + failed_when: > + 'data' not in opn15 or + opn15.data | length != 0 + +- name: Testing Rules - alias deletion if in use + # this is to fix lacking server-side checks for the automation-rules + # see: https://forum.opnsense.org/index.php?topic=30077.msg145259#msg145259 + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.rule: + match_fields: ['description'] + + oxlorg.opnsense.list: + target: 'rule' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Creating alias + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_3_3' + content: '192.168.0.1' + when: not ansible_check_mode + + - name: Creating rule + oxlorg.opnsense.rule: + source_net: 'ANSIBLE_TEST_3_3' + description: 'ANSIBLE_TEST_1_9' + protocol: 'TCP' + when: not ansible_check_mode + + - name: Deleting alias - should fail + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_3_3' + state: 'absent' + register: opn13 + failed_when: not opn13.failed + when: not ansible_check_mode + + - name: Cleanup rule + oxlorg.opnsense.rule: + description: 'ANSIBLE_TEST_1_9' + state: 'absent' + diff: false + when: not ansible_check_mode + + - name: Cleanup alias + oxlorg.opnsense.alias: + name: 'ANSIBLE_TEST_3_3' + state: 'absent' + diff: false + when: not ansible_check_mode + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn16 + failed_when: > + 'data' not in opn16 or + opn16.data | length != 0 + +- name: Testing Rules - Advanced Mode + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.rule: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + match_fields: ['description'] + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding 1 - failing because of invalid value + oxlorg.opnsense.rule: + tcp_flags: syn + description: 'ANSIBLE_TEST_4_1' + register: opn_fail1 + failed_when: not opn_fail1.failed + ignore_errors: true + + - name: Adding Filter + oxlorg.opnsense.rule: + protocol: TCP + allow_opts: true + tcp_flags: syn + tcp_flags_clear: ack + description: 'ANSIBLE_TEST_4_1' + + - name: Updating Filter - nothing changed + oxlorg.opnsense.rule: + protocol: TCP + allow_opts: true + tcp_flags: syn + tcp_flags_clear: ack + description: 'ANSIBLE_TEST_4_1' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + when: not ansible_check_mode + + - name: Updating Stateful firewall + oxlorg.opnsense.rule: + protocol: TCP + state_type: sloppy + state_policy: floating + state_timeout: 42 + max_src_nodes: 43 + max_src_states: 44 + max_src_conn: 45 + max_src_conn_rate: 46 + max_src_conn_rates: 47 + overload: virusprot + adaptive_start: 48 + adaptive_end: 99 + description: 'ANSIBLE_TEST_4_1' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Updating Stateful firewall - nothing changed + oxlorg.opnsense.rule: + protocol: TCP + state_type: sloppy + state_policy: floating + state_timeout: 42 + max_src_nodes: 43 + max_src_states: 44 + max_src_conn: 45 + max_src_conn_rate: 46 + max_src_conn_rates: 47 + overload: virusprot + adaptive_start: 48 + adaptive_end: 99 + description: 'ANSIBLE_TEST_4_1' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Updating Source Routing firewall + oxlorg.opnsense.rule: + gateway: TEST-GW + replyto: TEST-GW + disable_replyto: true + description: 'ANSIBLE_TEST_4_1' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Updating Source Routing - nothing changed + oxlorg.opnsense.rule: + gateway: TEST-GW + replyto: TEST-GW + disable_replyto: true + description: 'ANSIBLE_TEST_4_1' + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Updating Internal tagging + oxlorg.opnsense.rule: + tagged: ansible + tag: ansible + description: 'ANSIBLE_TEST_4_1' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Updating Internal tagging - nothing changed + oxlorg.opnsense.rule: + tagged: ansible + tag: ansible + description: 'ANSIBLE_TEST_4_1' + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Updating Priority + oxlorg.opnsense.rule: + prio: 1 + set_prio: 2 + set_prio_low: 3 + tos: af42 + description: 'ANSIBLE_TEST_4_1' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + + - name: Updating Priority - nothing changed + oxlorg.opnsense.rule: + prio: 1 + set_prio: 2 + set_prio_low: 3 + tos: af42 + description: 'ANSIBLE_TEST_4_1' + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Updating Internal tagging + oxlorg.opnsense.rule: + tagged: ansible + tag: ansible + description: 'ANSIBLE_TEST_4_1' + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + + - name: Updating Internal tagging - nothing changed + oxlorg.opnsense.rule: + tagged: ansible + tag: ansible + description: 'ANSIBLE_TEST_4_1' + register: opn11 + failed_when: > + opn11.failed or + opn11.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.rule: + description: "{{ item }}" + state: 'absent' + diff: false + loop: + - 'ANSIBLE_TEST_4_1' diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_interface_group.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_interface_group.yml new file mode 100644 index 0000000..1a67c9c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_interface_group.yml @@ -0,0 +1,92 @@ +--- + +- name: Testing Interface Groups + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'rule_interface_group' + + vars: + if_grp: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL_RULE_GRP_IF') | default('lan', true) }}" + default_grps: ['All OpenVPN interfaces', 'IPsec', 'WireGuard'] + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + (opn_pre1.data | length) != (default_grps | length) + + - name: Removing - does not exist + oxlorg.opnsense.rule_interface_group: + name: 'ANSIBLE_TEST' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid interface (server-side) + oxlorg.opnsense.rule_interface_group: + name: 'ANSIBLE_TEST' + members: 'abc' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Adding 1 - failing because of missing members + oxlorg.opnsense.rule_interface_group: + name: 'ANSIBLE_TEST' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.rule_interface_group: + name: 'ANSIBLE_TEST' + members: "{{ if_grp }}" + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 1 - nothing changed + oxlorg.opnsense.rule_interface_group: + name: 'ANSIBLE_TEST' + members: "{{ if_grp }}" + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + (opn3.data | length) != (default_grps | length + 1) + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.rule_interface_group: + name: 'ANSIBLE_TEST' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + (opn_clean1.data | length) != (default_grps | length) + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_multi.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_multi.yml new file mode 100644 index 0000000..92bae66 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_multi.yml @@ -0,0 +1,251 @@ +--- + +- name: Testing Multiple Rules + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'rule' + + oxlorg.opnsense.rule_multi: + match_fields: ['description'] + multi_control: + fail_verify: true + fail_process: true + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Removing - do not exist + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_1' + - name: 'ANSIBLE_TEST_2_2' + - name: 'ANSIBLE_TEST_2_3' + - name: 'ANSIBLE_TEST_2_4' + - name: 'ANSIBLE_TEST_2_5' + - name: 'ANSIBLE_TEST_2_6' + multi_control: + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_1' + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '10.0.0.0/8' + action: 'block' + - name: 'ANSIBLE_TEST_2_2' + source_net: '192.168.0.0/24' + destination_net: '192.168.10.0/24' + destination_port: 443 + protocol: 'TCP' + interface: 'lan' + - name: 'ANSIBLE_TEST_2_3' + source_invert: true + source_net: 'bogons' + action: 'block' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_1' + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '10.1.0.0/8' + action: 'block' + - name: 'ANSIBLE_TEST_2_2' + source_net: '192.168.0.0/24' + destination_net: '192.168.10.0/24' + destination_port: 8080 + protocol: 'TCP' + interface: ['lan'] + - name: 'ANSIBLE_TEST_2_3' + source_invert: true + source_net: 'bogons' + ip_protocol: 'inet6' + action: 'block' + when: not ansible_check_mode + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Changing - not changed + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_1' + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '10.1.0.0/8' + action: 'block' + - name: 'ANSIBLE_TEST_2_2' + source_net: '192.168.0.0/24' + destination_net: '192.168.10.0/24' + destination_port: 8080 + protocol: 'TCP' + interface: ['lan'] + - name: 'ANSIBLE_TEST_2_3' + source_invert: true + source_net: 'bogons' + ip_protocol: 'inet6' + action: 'block' + when: not ansible_check_mode + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + + - name: Adding 2 + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_4' + interface: 'opt1' + protocol: 'ICMP' + when: not ansible_check_mode + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + + - name: Changing 2 - not changed + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_4' + interface: 'opt1' + protocol: 'ICMP' + when: not ansible_check_mode + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + + - name: Fail on server-side validation + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_1' + - name: 'ANSIBLE_TEST_2_2' + destination_port: 'XXX' + - name: 'ANSIBLE_TEST_2_3' + register: opn6 + failed_when: not opn6.failed + when: not ansible_check_mode + + - name: Adding 3 - Advanced Mode + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_5' + allow_opts: true + tcp_flags: syn + tcp_flags_clear: ack + protocol: 'TCP' + - name: 'ANSIBLE_TEST_2_6' + protocol: TCP + state_type: sloppy + state_policy: floating + state_timeout: 42 + max_src_nodes: 43 + max_src_states: 44 + max_src_conn: 45 + max_src_conn_rate: 46 + max_src_conn_rates: 47 + overload: virusprot + adaptive_start: 48 + adaptive_end: 99 + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + + - name: Changing 3 - Advanced Mode - not changed + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_5' + allow_opts: true + tcp_flags: syn + tcp_flags_clear: ack + protocol: 'TCP' + - name: 'ANSIBLE_TEST_2_6' + protocol: TCP + state_type: sloppy + state_policy: floating + state_timeout: 42 + max_src_nodes: 43 + max_src_states: 44 + max_src_conn: 45 + max_src_conn_rate: 46 + max_src_conn_rates: 47 + overload: virusprot + adaptive_start: 48 + adaptive_end: 99 + when: not ansible_check_mode + register: opn11 + failed_when: > + opn11.failed or + opn11.changed + + # noting to validate.. + # - name: Fail on client-side validation + # oxlorg.opnsense.rule_multi: + # rules: + # - name: 'ANSIBLE_TEST_2_1' + # - name: 'ANSIBLE_TEST_2_2' + # destination_port: 'XXX' + # - name: 'ANSIBLE_TEST_2_3' + # register: opn6 + # failed_when: not opn6.failed + # + # - name: Don't fail on client-side validation + # oxlorg.opnsense.rule_multi: + # rules: + # - name: 'ANSIBLE_TEST_2_1' + # - name: 'ANSIBLE_TEST_2_2' + # destination_port: 'XXX' + # - name: 'ANSIBLE_TEST_2_3' + # fail_verification: false + # register: opn7 + # failed_when: opn7.failed + # when: ansible_check_mode # else the server-side will reject it + + - name: Listing rules + oxlorg.opnsense.list: + register: opn4 + failed_when: > + 'data' not in opn4 or + opn4.data | length != 6 + when: not ansible_check_mode + + - name: Removing + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_2_1' + - name: 'ANSIBLE_TEST_2_2' + - name: 'ANSIBLE_TEST_2_3' + - name: 'ANSIBLE_TEST_2_4' + - name: 'ANSIBLE_TEST_2_5' + - name: 'ANSIBLE_TEST_2_6' + multi_control: + state: 'absent' + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn5 + failed_when: > + 'data' not in opn5 or + opn5.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_purge.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_purge.yml new file mode 100644 index 0000000..5b3d2b0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/rule_purge.yml @@ -0,0 +1,234 @@ +--- + +- name: Testing Purging of Rules + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'rule' + + oxlorg.opnsense.rule_multi: + match_fields: ['description'] + multi_control: + fail_verify: true + fail_process: true + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Purge ALL + oxlorg.opnsense.rule_multi: + multi_control: + purge_all: true + + - name: Adding 1 + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_3_1' + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '10.0.0.0/8' + action: 'block' + - name: 'ANSIBLE_TEST_3_2' + source_net: '192.168.0.0/24' + destination_net: '192.168.10.0/24' + destination_port: 443 + protocol: 'TCP' + interface: 'lan' + - name: 'ANSIBLE_TEST_3_3' + source_invert: true + source_net: 'bogons' + action: 'block' + when: not ansible_check_mode + + - name: Filtered purge (single) + oxlorg.opnsense.rule_multi: + multi_control: + purge_filter: + destination_net: '192.168.10.0/24' + purge_action: 'disable' + when: not ansible_check_mode + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Simple disable-purge + oxlorg.opnsense.rule_multi: + multi_purge: + - name: 'ANSIBLE_TEST_3_1' + multi_control: + purge_action: 'disable' + register: opn2 + when: not ansible_check_mode + failed_when: > + opn2.failed or + not opn2.changed + + - name: Simple purge + oxlorg.opnsense.rule_multi: + multi_purge: + - name: 'ANSIBLE_TEST_3_1' + when: not ansible_check_mode + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + + - name: Listing rules + oxlorg.opnsense.list: + register: opn4 + failed_when: > + 'data' not in opn4 or + opn4.data | length != 2 + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_3_1' + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '192.168.10.0/24' + action: 'block' + - name: 'ANSIBLE_TEST_3_2' + source_net: '192.168.0.0/24' + destination_net: '192.168.10.0/24' + destination_port: 443 + protocol: 'TCP' + interface: 'lan' + - name: 'ANSIBLE_TEST_3_3' + source_invert: true + source_net: 'bogons' + action: 'block' + when: not ansible_check_mode + + - name: Filtered purge (single) + oxlorg.opnsense.rule_multi: + multi_control: + purge_filter: + destination_net: '192.168.10.0/24' + when: not ansible_check_mode + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Listing rules + oxlorg.opnsense.list: + register: opn5 + failed_when: > + 'data' not in opn5 or + opn5.data | length != 1 + when: not ansible_check_mode + + - name: Adding 3 + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_3_1' + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '192.168.10.0/24' + action: 'block' + - name: 'ANSIBLE_TEST_3_2' + source_net: '192.168.0.0/24' + destination_net: '192.168.10.0/24' + destination_port: 443 + protocol: 'TCP' + interface: 'lan' + - name: 'ANSIBLE_TEST_3_3' + source_invert: true + source_net: 'bogons' + action: 'block' + when: not ansible_check_mode + + - name: Filtered purge (single inverted) + oxlorg.opnsense.rule_multi: + multi_control: + purge_filter: + destination_net: '192.168.10.0/24' + purge_filter_invert: true + when: not ansible_check_mode + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Listing rules + oxlorg.opnsense.list: + register: opn7 + failed_when: > + 'data' not in opn7 or + opn7.data | length != 2 + when: not ansible_check_mode + + - name: Adding 4 + oxlorg.opnsense.rule_multi: + rules: + - name: 'ANSIBLE_TEST_3_1' + source_net: '192.168.1.0/24' + destination_invert: true + destination_net: '192.168.10.0/24' + action: 'block' + + - name: 'ANSIBLE_TEST_3_2' + source_net: '192.168.0.0/24' + destination_net: '192.168.10.0/24' + destination_port: 443 + protocol: 'TCP' + interface: 'lan' + action: 'pass' + + - name: 'ANSIBLE_TEST_3_3' + source_invert: true + source_net: 'bogons' + action: 'block' + when: not ansible_check_mode + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + + - name: Filtered purge (multiple) + oxlorg.opnsense.rule_multi: + multi_control: + purge_filter: + ip_protocol: 'inet' + action: 'block' + when: not ansible_check_mode + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + + - name: Listing rules + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Purge ALL + oxlorg.opnsense.rule_multi: + multi_control: + purge_all: true + when: not ansible_check_mode + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn11 + failed_when: > + 'data' not in opn11 or + opn11.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/savepoint.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/savepoint.yml new file mode 100644 index 0000000..8c54978 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/savepoint.yml @@ -0,0 +1,101 @@ +--- + +- name: Testing Savepoints + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.savepoint: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Canceling filter savepoint rollback - does not exist + oxlorg.opnsense.savepoint: + action: 'cancel_rollback' + controller: 'filter' + revision: 'non-existent' + + - name: Reverting filter savepoint - does not exist + oxlorg.opnsense.savepoint: + action: 'revert' + controller: 'filter' + revision: 'non-existent' + + - name: Creating filter savepoint + oxlorg.opnsense.savepoint: + action: 'create' + controller: 'filter' + register: opn1 + failed_when: > + opn1.failed or + 'revision' not in opn1 or + not opn1.revision + when: not ansible_check_mode + + - name: Applying filter savepoint + oxlorg.opnsense.savepoint: + action: 'apply' + controller: 'filter' + revision: "{{ opn1.revision }}" + when: not ansible_check_mode + + - name: Canceling filter savepoint rollback + oxlorg.opnsense.savepoint: + action: 'cancel' + controller: 'filter' + revision: "{{ opn1.revision }}" + when: not ansible_check_mode + + - name: Creating filter savepoint - to be reverted + oxlorg.opnsense.savepoint: + action: 'create' + controller: 'filter' + register: opn2 + failed_when: > + opn2.failed or + 'revision' not in opn2 or + not opn2.revision + when: not ansible_check_mode + + - name: Applying filter savepoint - to be reverted + oxlorg.opnsense.savepoint: + action: 'apply' + controller: 'filter' + revision: "{{ opn2.revision }}" + when: not ansible_check_mode + + - name: Reverting filter savepoint + oxlorg.opnsense.savepoint: + action: 'revert' + controller: 'filter' + revision: "{{ opn2.revision }}" + when: not ansible_check_mode + + - name: Creating source-nat savepoint + oxlorg.opnsense.savepoint: + action: 'create' + controller: 'source_nat' + register: opn4 + failed_when: > + opn4.failed or + 'revision' not in opn4 or + not opn4.revision + when: not ansible_check_mode + + - name: Applying source-nat savepoint + oxlorg.opnsense.savepoint: + action: 'apply' + controller: 'source_nat' + revision: "{{ opn4.revision }}" + when: not ansible_check_mode + + - name: Canceling source-nat savepoint rollback + oxlorg.opnsense.savepoint: + action: 'cancel_rollback' + controller: 'source_nat' + revision: "{{ opn4.revision }}" + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/service.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/service.yml new file mode 100644 index 0000000..3ceb6ee --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/service.yml @@ -0,0 +1,107 @@ +--- + +- name: Testing Service + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.service: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + timeout: 60 + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Checking syslog status + oxlorg.opnsense.service: + name: 'syslog' + action: 'status' + register: opn1 + failed_when: > + opn1.failed or + 'data' not in opn1 or + opn1.data.status != 'running' + + - name: Stopping syslog + oxlorg.opnsense.service: + name: 'syslog' + action: 'stop' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Checking syslog status (should be stopped) + oxlorg.opnsense.service: + name: 'syslog' + action: 'status' + register: opn3 + failed_when: > + opn3.failed or + 'data' not in opn3 or + opn3.data.status != 'stopped' + when: not ansible_check_mode + + - name: Starting syslog + oxlorg.opnsense.service: + name: 'syslog' + action: 'start' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Checking syslog status (should be running again) + oxlorg.opnsense.service: + name: 'syslog' + action: 'status' + register: opn5 + failed_when: > + opn5.failed or + 'data' not in opn5 or + opn5.data.status != 'running' + when: not ansible_check_mode + + - name: Restarting syslog + oxlorg.opnsense.service: + name: 'syslog' + action: 'restart' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Reloading syslog + oxlorg.opnsense.service: + name: 'syslog' + action: 'reload' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Checking syslog status (should still be running) + oxlorg.opnsense.service: + name: 'syslog' + action: 'status' + register: opn8 + failed_when: > + opn8.failed or + 'data' not in opn8 or + opn8.data.status != 'running' + + - name: Restarting traffic-shaper (flush & reload) + oxlorg.opnsense.service: + name: 'shaper' + action: 'restart' + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_pipe.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_pipe.yml new file mode 100644 index 0000000..419ccc0 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_pipe.yml @@ -0,0 +1,165 @@ +--- + +- name: Testing Shaper Pipes + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'shaper_pipe' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_1' + bandwidth: 50 + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of invalid int-value + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_1' + bandwidth: 50 + queue: 999 + register: opn11 + failed_when: not opn11.failed + + - name: Adding 1 - failing because of unprovided bandwidth + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_1' + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_1' + bandwidth: 50 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Changing 1 + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_1' + bandwidth: 60 + queue: 20 + pie_enable: true + timeout: 60 + register: opn14 + failed_when: > + opn14.failed or + not opn14.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_1' + bandwidth: 60 + queue: 20 + pie_enable: true + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_1' + bandwidth: 60 + queue: 20 + pie_enable: true + enabled: false + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_1' + bandwidth: 60 + queue: 20 + pie_enable: true + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 & flushing/resetting running config + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_2' + bandwidth: 750 + bandwidth_metric: 'Kbit' + reset: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_2' + bandwidth: 750 + bandwidth_metric: 'Kbit' + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.shaper_pipe: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_queue.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_queue.yml new file mode 100644 index 0000000..00be16f --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_queue.yml @@ -0,0 +1,188 @@ +--- + +- name: Testing Shaper Queues + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'shaper_queue' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Adding dummy pipe + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_2_1' + bandwidth: 50 + check_mode: false + + - name: Removing - does not exist + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'ANSIBLE_TEST_2_1' + weight: 50 + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of invalid int-value + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'ANSIBLE_TEST_2_1' + weight: 999 + register: opn11 + failed_when: not opn11.failed + + - name: Adding 1 - failing because of unprovided weight + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'ANSIBLE_TEST_2_1' + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 - failing because of non-existant pipe + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'does-not-exist' + register: opn13 + failed_when: not opn13.failed + + - name: Adding 1 + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'ANSIBLE_TEST_2_1' + weight: 50 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Changing 1 + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'ANSIBLE_TEST_2_1' + weight: 60 + pie_enable: true + register: opn14 + failed_when: > + opn14.failed or + not opn14.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'ANSIBLE_TEST_2_1' + weight: 60 + pie_enable: true + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'ANSIBLE_TEST_2_1' + weight: 60 + pie_enable: true + enabled: false + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_1' + pipe: 'ANSIBLE_TEST_2_1' + weight: 60 + pie_enable: true + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 & flushing/resetting running config + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_2' + pipe: 'ANSIBLE_TEST_2_1' + weight: 75 + mask: 'src-ip' + reset: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_2' + pipe: 'ANSIBLE_TEST_2_1' + weight: 75 + mask: 'src-ip' + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.shaper_queue: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Cleanup pipe + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_2_1' + state: 'absent' + check_mode: false + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_rule.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_rule.yml new file mode 100644 index 0000000..3cde854 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/shaper_rule.yml @@ -0,0 +1,240 @@ +--- + +- name: Testing Shaper Rules + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'shaper_rule' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Adding dummy pipe + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_3_1' + bandwidth: 50 + check_mode: false + + - name: Adding dummy queue + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_2_1' + pipe: 'ANSIBLE_TEST_3_1' + weight: 50 + check_mode: false + + - name: Removing - does not exist + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_pipe: 'ANSIBLE_TEST_3_1' + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of invalid int-value + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_pipe: 'ANSIBLE_TEST_3_1' + max_packet_length: 100000 + register: opn11 + failed_when: not opn11.failed + + - name: Adding 1 - failing because of unprovided target + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 - failing because of non-existant pipe + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_pipe: 'does-not-exist' + register: opn13 + failed_when: not opn13.failed + + - name: Adding 1 - failing because of non-existant queue + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_queue: 'does-not-exist' + register: opn14 + failed_when: not opn14.failed + + - name: Adding 1 + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_queue: 'ANSIBLE_TEST_2_1' + protocol: 'tcp' + destination_port: 80-90 + sequence: 40 + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Changing 1 + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_queue: 'ANSIBLE_TEST_2_1' + protocol: 'tcp' + destination_port: 443 + sequence: 50 + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_queue: 'ANSIBLE_TEST_2_1' + protocol: 'tcp' + destination_port: 443 + enabled: false + sequence: 50 + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_queue: 'ANSIBLE_TEST_2_1' + protocol: 'tcp' + destination_port: 443 + enabled: false + sequence: 50 + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_1' + target_queue: 'ANSIBLE_TEST_2_1' + protocol: 'tcp' + destination_port: 443 + sequence: 50 + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 & flushing/resetting running config + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_2' + target_pipe: 'ANSIBLE_TEST_3_1' + destination_invert: true + destination: '172.16.0.0/12' + sequence: 60 + reset: true + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_2' + target_pipe: 'ANSIBLE_TEST_3_1' + destination_invert: true + destination: '172.16.0.0/12' + sequence: 60 + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Adding 2 - changing + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_2' + target_pipe: 'ANSIBLE_TEST_3_1' + destination_invert: true + destination: ['172.16.0.0/12', '192.168.0.0/29'] + sequence: 60 + register: opn17 + failed_when: > + opn17.failed or + not opn17.changed + when: not ansible_check_mode + + - name: Adding 2 - nothing changing + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_2' + target_pipe: 'ANSIBLE_TEST_3_1' + destination_invert: true + destination: ['172.16.0.0/12', '192.168.0.0/29'] + sequence: 60 + register: opn18 + failed_when: > + opn18.failed or + opn18.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.shaper_rule: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.shaper_rule: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Cleanup queue + oxlorg.opnsense.shaper_queue: + description: 'ANSIBLE_TEST_2_1' + state: 'absent' + check_mode: false + + - name: Cleanup pipe + oxlorg.opnsense.shaper_pipe: + description: 'ANSIBLE_TEST_3_1' + state: 'absent' + check_mode: false + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/snapshot.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/snapshot.yml new file mode 100644 index 0000000..cff630e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/snapshot.yml @@ -0,0 +1,166 @@ +--- + +- name: Testing Snapshot + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'snapshot' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Checking filesystem (only works for ZFS) + oxlorg.opnsense.snapshot: + name: 'ANSIBLE_TEST_1_1' + failed_when: false + check_mode: false + changed_when: false + register: opn_test + + - name: Setting filesystem status + ansible.builtin.set_fact: + opn_zfs: "{{ 'Unsupported' not in opn_test.msg | default('') }}" + + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + when: opn_zfs | bool + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 1 + + - name: Removing - does not exist + oxlorg.opnsense.snapshot: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + when: opn_zfs | bool + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Adding 1 - failing because of invalid name + oxlorg.opnsense.snapshot: + name: 'INVALID NAME' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.snapshot: + name: 'ANSIBLE_TEST_1_1' + register: opn1 + when: opn_zfs | bool + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding 2 + oxlorg.opnsense.snapshot: + name: 'ANSIBLE_TEST_1_2' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Adding 2 - nothing changed + oxlorg.opnsense.snapshot: + name: 'ANSIBLE_TEST_1_2' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Activate 2 + oxlorg.opnsense.snapshot: + name: 'ANSIBLE_TEST_1_2' + activate: true + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.snapshot: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn5 + failed_when: > + not opn5.failed or + opn5.changed + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Activate default + oxlorg.opnsense.snapshot: + name: 'default' + activate: true + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.snapshot: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 2 + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.snapshot: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: + - opn_zfs | bool + - not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 1 + when: + - opn_zfs | bool + - not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/syslog.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/syslog.yml new file mode 100644 index 0000000..d7360df --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/syslog.yml @@ -0,0 +1,133 @@ +--- + +# todo: test default matching + +- name: Testing Syslog + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.syslog: + match_fields: ['description'] + + oxlorg.opnsense.list: + target: 'syslog' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Removing - does not exist + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_1' + target: '192.168.0.1' + state: 'absent' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + + - name: Adding 1 + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_1' + target: '192.168.0.1' + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Failing tls validition + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_3' + target: '192.168.0.2' + transport: 'tls4' + register: opn9 + failed_when: not opn9.failed + + - name: Failing IPv4 validition + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_3' + target: 'd563:4c4e:1919:0914:3489:1d43:6264:ac73' + transport: 'tcp4' + register: opn10 + failed_when: not opn10.failed + + - name: Failing IPv6 validition + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_3' + target: '192.168.0.1' + transport: 'tcp6' + register: opn11 + failed_when: not opn11.failed + + - name: Adding 2 - DNS target + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_2' + target: 'test.oxlorg.net' + transport: 'tcp6' + timeout: 60 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Disabling 1 + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_1' + target: '192.168.0.1' + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_1' + target: '192.168.0.1' + enabled: false + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.syslog: + description: 'ANSIBLE_TEST_1' + target: '192.168.0.1' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn1 + failed_when: > + 'data' not in opn1 or + opn1.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.syslog: + description: "{{ item }}" + target: '192.168.0.1' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + - 'ANSIBLE_TEST_3' + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/system.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/system.yml new file mode 100644 index 0000000..363ea71 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/system.yml @@ -0,0 +1,47 @@ +--- + +- name: Testing System + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.system: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Fetching updates + oxlorg.opnsense.system: + action: 'update' + + - name: Upgrade with wait - failing because not forced + oxlorg.opnsense.system: + action: 'upgrade' + wait: true + timeout: 120 + poll_interval: 2 + register: opn_up1 + failed_when: not opn_up1.failed + + - name: Upgrade with wait + oxlorg.opnsense.system: + action: 'upgrade' + force_upgrade: true + wait: true + timeout: 120 + poll_interval: 2 + + - name: Wait for system to start + ansible.builtin.pause: + seconds: 10 + when: not ansible_check_mode + + - name: Rebooting with wait + oxlorg.opnsense.system: + action: 'reboot' + wait: true + timeout: 90 + poll_interval: 2 diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_acl.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_acl.yml new file mode 100644 index 0000000..7496cb9 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_acl.yml @@ -0,0 +1,201 @@ +--- + +- name: Testing Unbound ACLs + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'unbound_acl' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 or + opn_pre1.data | length > 0 + + - name: Adding - failing because an invalid action was provided + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + action: 'INVALID_ACTION' + networks: ['192.168.0.0/24'] + reload: false + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding - failing because no network was provided + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + reload: false + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding - failing because an invalid network was provided + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + networks: ['192.168.0.0/258'] + reload: false + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding - failing because of an invalid description was provided + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + networks: ['192.168.0.0/24'] + description: >- + 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + 12345678901234567890123456789012345678901234567890123456 + reload: false + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + action: 'deny' + networks: ['192.168.0.0/24'] + description: 'Deny access' + reload: false # speed + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + action: 'deny' + networks: ['192.168.0.0/25'] + description: 'Deny access' + reload: false # speed + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Changing 1 - more + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + action: 'deny' + networks: ['10.10.0.0/29', '192.168.0.0/25'] + description: 'Deny access' + reload: false # speed + register: opn10 + failed_when: > + opn10.failed or + not opn10.changed + + - name: Disabling 1 + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + action: 'deny' + networks: ['10.10.0.0/29', '192.168.0.0/25'] + description: 'Deny access' + enabled: false + reload: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + action: 'deny' + networks: ['10.10.0.0/29', '192.168.0.0/25'] + description: 'Deny access' + enabled: false + reload: false # speed + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_1' + action: 'deny' + networks: ['10.10.0.0/29', '192.168.0.0/25'] + description: 'Deny access' + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_2' + action: 'allow_snoop' + networks: ['192.168.0.128/29', '192.168.0.192/29'] + description: 'Allow snoop' + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Adding 2 - Nothing changed + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_2' + action: 'allow_snoop' + networks: ['192.168.0.128/29', '192.168.0.192/29'] + description: 'Allow snoop' + reload: false # speed + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.unbound_acl: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + reload: false # speed + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn9 + failed_when: > + 'data' not in opn9 or + opn9.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.unbound_acl: + name: "{{ item }}" + state: 'absent' + reload: false + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_dnsbl.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_dnsbl.yml new file mode 100644 index 0000000..5d31867 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_dnsbl.yml @@ -0,0 +1,179 @@ +--- + +- name: Testing Unbound DNS-BL settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'unbound_dnsbl' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + opn_pre1.data | length != 0 + + - name: Configuring - failing because of invalid type + oxlorg.opnsense.unbound_dnsbl: + name: 'ANSIBLE_TEST_1' + providers: + - 'INVALID' + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Configuring - failing because of invalid address + oxlorg.opnsense.unbound_dnsbl: + name: 'ANSIBLE_TEST_1' + address: 'INVALID' + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Adding 1 + oxlorg.opnsense.unbound_dnsbl: + name: 'ANSIBLE_TEST_1' + providers: 'atf' + download_urls: ['https://example.com/dns.blocklist'] + domains_allow: + - 'opnsense.org' + - 'oxlorg.net' + domains_block: + - 'example.tor' + wildcard_domains_block: + - 'example.tor' + source_networks: + - '10.0.10.0/24' + - '10.2.10.0/24' + nxdomain_address: '192.168.255.255' + nxdomain: false + enabled: true + reload: false # speed + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.unbound_dnsbl: + name: 'ANSIBLE_TEST_1' + providers: 'atf' + domains_allow: + - 'opnsense.org' + - 'oxl.at' + domains_block: + - 'example.tor' + wildcard_domains_block: + - 'example.tor' + source_networks: + - '10.10.10.0/24' + - '10.3.10.0/24' + nxdomain: true + enabled: true + reload: false # speed + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.unbound_dnsbl: + name: 'ANSIBLE_TEST_1' + providers: 'atf' + domains_allow: + - 'opnsense.org' + - 'oxl.at' + domains_block: + - 'example.tor' + wildcard_domains_block: + - 'example.tor' + source_networks: + - '10.10.10.0/24' + - '10.3.10.0/24' + nxdomain: true + enabled: false + reload: false # speed + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.unbound_dnsbl: + name: 'ANSIBLE_TEST_1' + providers: 'atf' + domains_allow: + - 'opnsense.org' + - 'oxl.at' + domains_block: + - 'example.tor' + wildcard_domains_block: + - 'example.tor' + source_networks: + - '10.10.10.0/24' + - '10.3.10.0/24' + nxdomain: true + enabled: false + reload: false # speed + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn6 + failed_when: > + opn6.failed or + opn6.data | length != 1 + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.unbound_dnsbl: + name: 'ANSIBLE_TEST_2' + providers: 'atf' + download_urls: ['https://example.com/dns.blocklist'] + domains_block: + - 'example.tor' + source_networks: + - '10.0.10.0/24' + - '10.2.10.0/24' + nxdomain: false + cache_ttl: 1000000 + enabled: true + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + opn7.failed or + opn7.data | length != 2 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.unbound_dnsbl: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1' + - 'ANSIBLE_TEST_2' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_dot.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_dot.yml new file mode 100644 index 0000000..775f74d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_dot.yml @@ -0,0 +1,189 @@ +--- + +# todo: test default matching + +- name: Testing Unbound DNS-over-TLS + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.unbound_dot: + timeout: 60 + + oxlorg.opnsense.list: + target: 'unbound_dot' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing dots + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.unbound_dot: + domain: 'dot.opnsense.test.oxlorg.net' + target: '1.1.1.1' + verify: 'dot.opnsense.test.oxlorg.net' + state: 'absent' + reload: false + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of invalid verfiy-content + oxlorg.opnsense.unbound_dot: + domain: 'dot.opnsense.test.oxlorg.net' + target: '1.1.1.1' + verify: 'Some CN' + reload: false # speed + register: opn11 + failed_when: not opn11.failed + + - name: Adding 1 + oxlorg.opnsense.unbound_dot: + domain: 'dot.opnsense.test.oxlorg.net' + target: '1.1.1.1' + verify: 'dot.opnsense.test.oxlorg.net' + reload: false # speed + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Adding 2 + oxlorg.opnsense.unbound_dot: + domain: 'dot.opnsense.test.oxlorg.net' + target: '1.1.1.2' + verify: 'dot.opnsense.test.oxlorg.net' + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Disabling 2 + oxlorg.opnsense.unbound_dot: + domain: 'dot.opnsense.test.oxlorg.net' + target: '1.1.1.2' + verify: 'dot.opnsense.test.oxlorg.net' + enabled: false + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 2 - nothing changed + oxlorg.opnsense.unbound_dot: + domain: 'dot.opnsense.test.oxlorg.net' + target: '1.1.1.2' + verify: 'dot.opnsense.test.oxlorg.net' + enabled: false + reload: false # speed + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 2 + oxlorg.opnsense.unbound_dot: + domain: 'dot.opnsense.test.oxlorg.net' + target: '1.1.1.2' + verify: 'dot.opnsense.test.oxlorg.net' + reload: false # speed + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.unbound_dot: + domain: 'dot.opnsense.test.oxlorg.net' + target: '1.1.1.2' + verify: 'dot.opnsense.test.oxlorg.net' + state: 'absent' + reload: false # speed + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing dots + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Adding 3 without domain + oxlorg.opnsense.unbound_dot: + target: '1.1.1.3' + verify: 'dot.opnsense.test.oxlorg.net' + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Changing 3 port + oxlorg.opnsense.unbound_dot: + target: '1.1.1.3' + verify: 'dot.opnsense.test.oxlorg.net' + port: 853 + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Removing 3 + oxlorg.opnsense.unbound_dot: + target: '1.1.1.3' + verify: 'dot.opnsense.test.oxlorg.net' + state: 'absent' + reload: false + when: not ansible_check_mode + + - name: Adding 4 without domain or verify + oxlorg.opnsense.unbound_dot: + target: '1.1.1.4' + reload: false # speed + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + + - name: Cleanup + oxlorg.opnsense.unbound_dot: + target: "{{ item }}" + state: 'absent' + reload: false + loop: + - '1.1.1.1' + - '1.1.1.2' + - '1.1.1.3' + - '1.1.1.4' + when: not ansible_check_mode + + - name: Listing dots + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_forward.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_forward.yml new file mode 100644 index 0000000..aa9157e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_forward.yml @@ -0,0 +1,179 @@ +--- + +# todo: test default matching + +- name: Testing Unbound DNS-Forwarding + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.unbound_forward: + timeout: 60 + + oxlorg.opnsense.list: + target: 'unbound_forward' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.unbound_forward: + domain: 'fwd.opnsense.test.oxlorg.net' + target: '1.1.1.1' + state: 'absent' + reload: false + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 + oxlorg.opnsense.unbound_forward: + domain: 'fwd.opnsense.test.oxlorg.net' + target: '1.1.1.1' + reload: false # speed + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Adding 2 + oxlorg.opnsense.unbound_forward: + domain: 'fwd.opnsense.test.oxlorg.net' + target: '1.1.1.2' + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 3 - catch-all + oxlorg.opnsense.unbound_forward: + target: '1.1.1.3' + reload: false # speed + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + + - name: Disabling 2 + oxlorg.opnsense.unbound_forward: + domain: 'fwd.opnsense.test.oxlorg.net' + target: '1.1.1.2' + enabled: false + reload: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 2 - nothing changed + oxlorg.opnsense.unbound_forward: + domain: 'fwd.opnsense.test.oxlorg.net' + target: '1.1.1.2' + enabled: false + reload: false # speed + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 2 + oxlorg.opnsense.unbound_forward: + domain: 'fwd.opnsense.test.oxlorg.net' + target: '1.1.1.2' + reload: false # speed + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Channging 2 - tcp-forward + oxlorg.opnsense.unbound_forward: + domain: 'fwd.opnsense.test.oxlorg.net' + target: '1.1.1.2' + forward_tcp: true + reload: false # speed + register: opn12 + failed_when: > + opn12.failed or + not opn12.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.unbound_forward: + domain: 'fwd.opnsense.test.oxlorg.net' + target: '1.1.1.2' + state: 'absent' + reload: false # speed + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 2 + when: not ansible_check_mode + + - name: Adding 4 - description + oxlorg.opnsense.unbound_forward: + target: '1.1.1.4' + reload: false # speed + description: 'Deny access' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Adding - failing because of an invalid description was provided + oxlorg.opnsense.unbound_forward: + target: '1.1.1.4' + reload: false # speed + description: >- + 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + 123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + 12345678901234567890123456789012345678901234567890123456 + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.unbound_forward: + domain: "{{ item.d }}" + target: "{{ item.t }}" + state: 'absent' + reload: false + loop: + - {d: 'fwd.opnsense.test.oxlorg.net', t: '1.1.1.1'} + - {d: 'fwd.opnsense.test.oxlorg.net', t: '1.1.1.2'} + - {d: '', t: '1.1.1.3'} + - {d: '', t: '1.1.1.4'} + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_general.yml new file mode 100644 index 0000000..f185053 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_general.yml @@ -0,0 +1,197 @@ +--- + +- name: Testing Unbound DNS general settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'unbound_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid port + oxlorg.opnsense.unbound_general: + port: 0 + reload: false + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of non-existent interface + oxlorg.opnsense.unbound_general: + interfaces: + - 'DOES-NOT-EXIST' + reload: false + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring - failing because of invalid DNS64 prefix + oxlorg.opnsense.unbound_general: + dns64_prefix: 'INVALID-PREFIX' + reload: false + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Configuring - failing because of invalid DHCP domain + oxlorg.opnsense.unbound_general: + dhcp_domain: '!INVALID-DOMAIN!' + reload: false + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Configuring - failing because of invalid local zone type + oxlorg.opnsense.unbound_general: + local_zone_type: 'INVALID-TYPE' + reload: false + register: opn_fail5 + failed_when: not opn_fail5.failed + + - name: Configuring - failing because of non-existent outgoing interface + oxlorg.opnsense.unbound_general: + outgoing_interfaces: + - 'DOES-NOT-EXIST' + reload: false + register: opn_fail6 + failed_when: not opn_fail6.failed + + - name: Configuring + oxlorg.opnsense.unbound_general: + port: 5353 + interfaces: + - 'lan' + local_zone_type: 'always_nxdomain' + enabled: true + reload: false # speed + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing + oxlorg.opnsense.unbound_general: + port: 53 + interfaces: + - 'lan' + local_zone_type: 'transparent' + enabled: true + reload: false # speed + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Disabling 1 + oxlorg.opnsense.unbound_general: + port: 53 + interfaces: + - 'lan' + local_zone_type: 'transparent' + enabled: false + reload: false # speed + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.unbound_general: + port: 53 + interfaces: + - 'lan' + local_zone_type: 'transparent' + enabled: false + reload: false # speed + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.unbound_general: + port: 53 + interfaces: + - 'lan' + local_zone_type: 'transparent' + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Changing more + oxlorg.opnsense.unbound_general: + port: 53 + interfaces: + - 'lan' + dnssec: true + dns64: true + dns64_prefix: '48:ff9b::/96' + aaaa_only_mode: true + register_dhcp_leases: true + dhcp_domain: 'example.org' + register_dhcp_static_mappings: true + register_ipv6_link_local: false + register_system_records: false + txt: true + flush_dns_cache: true + local_zone_type: 'transparent' + outgoing_interfaces: + - 'lan' + wpad: true + enabled: true + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.unbound_general: + port: 53 + interfaces: + - 'lan' + dnssec: true + dns64: true + dns64_prefix: '48:ff9b::/96' + aaaa_only_mode: true + register_dhcp_leases: true + dhcp_domain: 'example.org' + register_dhcp_static_mappings: true + register_ipv6_link_local: false + register_system_records: false + txt: true + flush_dns_cache: true + local_zone_type: 'transparent' + outgoing_interfaces: + - 'lan' + wpad: true + enabled: true + reload: false # speed + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.unbound_general: + enabled: false + reload: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_host.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_host.yml new file mode 100644 index 0000000..59149df --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_host.yml @@ -0,0 +1,199 @@ +--- + +# todo: test default matching + +- name: Testing Unbound Host overrides + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.unbound_host: + match_fields: ['description'] + timeout: 60 + + oxlorg.opnsense.list: + target: 'unbound_host' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.unbound_host: + hostname: 'host' + domain: 'opnsense.test.oxlorg.net' + value: '192.168.0.1' + state: 'absent' + reload: false + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding - failing because of invalid values + oxlorg.opnsense.unbound_host: + description: 'ANSIBLE_TEST_1_1' + hostname: 'host' + domain: 'opnsense.test.oxlorg.net' + value: "{{ item.v }}" + type: "{{ item.t }}" + reload: false + register: opn_fail1 + failed_when: not opn_fail1.failed + loop: + - {t: 'A', v: 'not-an-ip'} + - {t: 'A', v: "{'msg': '192.168.0.1', 'invalid': 'data'}"} + - {t: 'A', v: '2001:db8::1'} + - {t: 'AAAA', v: 'not-an-ip'} + - {t: 'AAAA', v: '192.168.0.1'} + - {t: 'MX', v: 'not-a-hostname'} + - {t: 'MX', v: '192.168.0.1'} + + - name: Adding 1 + oxlorg.opnsense.unbound_host: + hostname: 'host' + domain: 'opnsense.test.oxlorg.net' + value: '192.168.0.1' + description: 'ANSIBLE_TEST_1_1' + reload: false # speed + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Disabling 1 + oxlorg.opnsense.unbound_host: + hostname: 'host' + domain: 'opnsense.test.oxlorg.net' + value: '192.168.0.1' + description: 'ANSIBLE_TEST_1_1' + enabled: false + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.unbound_host: + hostname: 'host' + domain: 'opnsense.test.oxlorg.net' + value: '192.168.0.1' + description: 'ANSIBLE_TEST_1_1' + enabled: false + reload: false # speed + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.unbound_host: + hostname: 'host' + domain: 'opnsense.test.oxlorg.net' + value: '192.168.0.1' + description: 'ANSIBLE_TEST_1_1' + reload: false # speed + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.unbound_host: + hostname: 'mx' + domain: 'opnsense.test.oxlorg.net' + value: 'host.opnsense.test.oxlorg.net' + record_type: 'MX' + prio: 5 + description: 'ANSIBLE_TEST_1_2' + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.unbound_host: + hostname: 'mx' + domain: 'opnsense.test.oxlorg.net' + value: 'host.opnsense.test.oxlorg.net' + record_type: 'MX' + prio: 5 + description: 'ANSIBLE_TEST_1_2' + reload: false # speed + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.unbound_host: + description: 'ANSIBLE_TEST_1_2' + hostname: 'dummy' + domain: 'dummy' + value: '192.168.0.1' + state: 'absent' + reload: false # speed + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Adding 3 - TLD-domain + oxlorg.opnsense.unbound_host: + hostname: 'host' + domain: 'local' + value: '192.168.0.1' + description: 'ANSIBLE_TEST_1_3' + reload: false # speed + register: opn14 + failed_when: > + opn14.failed or + not opn14.changed + + - name: Cleanup + oxlorg.opnsense.unbound_host: + description: "{{ item }}" + hostname: 'dummy' + domain: 'dummy' + value: '192.168.0.1' + state: 'absent' + reload: false + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_host_alias.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_host_alias.yml new file mode 100644 index 0000000..8694641 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/unbound_host_alias.yml @@ -0,0 +1,203 @@ +--- + +# todo: test default matching + +- name: Testing Unbound Host-Alias + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.unbound_host: + match_fields: ['description'] + timeout: 60 + + oxlorg.opnsense.unbound_host_alias: + match_fields: ['description'] + timeout: 60 + + oxlorg.opnsense.list: + target: 'unbound_host_alias' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Adding alias to link + oxlorg.opnsense.unbound_host: + hostname: 'host' + domain: 'opnsense.test.oxlorg.net' + value: '192.168.0.1' + description: 'ANSIBLE_TEST_2_1' + reload: false # speed + + - name: Removing - does not exist + oxlorg.opnsense.unbound_host_alias: + alias: 'e' + domain: 'opnsense.test.oxlorg.net' + target: 'host.opnsense.test.oxlorg.net' + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + reload: false + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + when: not ansible_check_mode + + - name: Adding 1 - failing because of invalid target + oxlorg.opnsense.unbound_host_alias: + alias: 'a' + domain: 'opnsense.test.oxlorg.net' + target: 'does.not.exist' + reload: false + register: opn11 + failed_when: not opn11.failed + + - name: Adding 1 - failing because of invalid domain + oxlorg.opnsense.unbound_host_alias: + alias: 'b' + domain: '!INVALID-DOMAIN!' + target: 'host.opnsense.test.oxlorg.net' + reload: false + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 + oxlorg.opnsense.unbound_host_alias: + alias: 'c' + domain: 'opnsense.test.oxlorg.net' + target: 'host.opnsense.test.oxlorg.net' + description: 'ANSIBLE_TEST_1_1' + reload: false # speed + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.unbound_host_alias: + alias: 'c' + domain: 'opnsense.test.oxlorg.net' + target: 'host.opnsense.test.oxlorg.net' + description: 'ANSIBLE_TEST_1_1' + enabled: false + reload: false # speed + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.unbound_host_alias: + alias: 'c' + domain: 'opnsense.test.oxlorg.net' + target: 'host.opnsense.test.oxlorg.net' + description: 'ANSIBLE_TEST_1_1' + enabled: false + reload: false # speed + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.unbound_host_alias: + alias: 'c' + domain: 'opnsense.test.oxlorg.net' + target: 'host.opnsense.test.oxlorg.net' + description: 'ANSIBLE_TEST_1_1' + reload: false # speed + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.unbound_host_alias: + alias: 'd' + domain: 'opnsense.test.oxlorg.net' + target: 'host.opnsense.test.oxlorg.net' + description: 'ANSIBLE_TEST_1_2' + reload: false # speed + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 - nothing changed + oxlorg.opnsense.unbound_host_alias: + alias: 'd' + domain: 'opnsense.test.oxlorg.net' + target: 'host.opnsense.test.oxlorg.net' + description: 'ANSIBLE_TEST_1_2' + reload: false # speed + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.unbound_host_alias: + description: 'ANSIBLE_TEST_1_2' + alias: 'd' + domain: 'opnsense.test.oxlorg.net' + state: 'absent' + reload: false # speed + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.unbound_host_alias: + description: "{{ item }}" + alias: 'dummy' + domain: 'dummy' + state: 'absent' + reload: false + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Cleanup host + oxlorg.opnsense.unbound_host: + hostname: 'host' + domain: 'opnsense.test.oxlorg.net' + description: 'ANSIBLE_TEST_2_1' + state: 'absent' + reload: false + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/user.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/user.yml new file mode 100644 index 0000000..5dec62b --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/user.yml @@ -0,0 +1,309 @@ +--- + +- name: Testing Access User + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'user' + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | selectattr('scope', 'eq', 'user') | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.user: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn_pre2 + failed_when: > + opn_pre2.failed or + opn_pre2.changed + + - name: Removing 1 - failing because of system user + oxlorg.opnsense.user: + name: 'root' + state: 'absent' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_1 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_1 + description: ANSIBLE_TEST_1_1 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Setting Password 1 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_1 + description: ANSIBLE_TEST_1_1 + scrambled_password: true + update_password: on_create + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Setting Password 1 - nothing changed + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_1 + description: ANSIBLE_TEST_1_1 + password: foo + update_password: on_create + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_1 + description: ANSIBLE_TEST_1_1 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_1 + description: ANSIBLE_TEST_1_1 + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_1 + description: ANSIBLE_TEST_1_1 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + register: opn7 + failed_when: > + opn7.failed or + opn7.changed + when: not ansible_check_mode + + - name: Changing 2 - grant privileges + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + privilege: + - user-config-readonly + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Changing 2 - grant privileges - nothing changed + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + privilege: + - user-config-readonly + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Changing 2 - nothing changed + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + register: opn10 + failed_when: > + opn10.failed or + opn10.changed + when: not ansible_check_mode + + - name: Changing 2 - revoking privileges + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + privilege: [] + register: opn11 + failed_when: > + opn11.failed or + not opn11.changed + when: not ansible_check_mode + + - name: Changing 2 - revoking privileges - nothing changed + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_2 + description: ANSIBLE_TEST_1_2 + privilege: [] + register: opn12 + failed_when: > + opn12.failed or + opn12.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_1_2 + state: 'absent' + register: opn13 + failed_when: > + opn13.failed or + not opn13.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn14 + failed_when: > + 'data' not in opn14 or + opn14.data | selectattr('scope', 'eq', 'user') | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.user: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | selectattr('scope', 'eq', 'user')| length != 0 + when: not ansible_check_mode + +- name: Testing Access User Group Membership + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'user' + + tasks: + - name: Adding Dummy Group + oxlorg.opnsense.group: + name: '{{ item }}' + loop: + - ANSIBLE_TEST_2_1 + - ANSIBLE_TEST_2_2 + + - name: Adding 1 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_2_1 + membership: ANSIBLE_TEST_2_1 + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + + - name: Changing 1 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_2_1 + membership: + - ANSIBLE_TEST_2_1 + - ANSIBLE_TEST_2_2 + register: opn16 + failed_when: > + opn16.failed or + not opn16.changed + + - name: Changing 1 - nothing changed + oxlorg.opnsense.user: + name: ANSIBLE_TEST_2_1 + membership: + - ANSIBLE_TEST_2_1 + - ANSIBLE_TEST_2_2 + register: opn17 + failed_when: > + opn17.failed or + opn17.changed + when: not ansible_check_mode + + - name: Changing 1 + oxlorg.opnsense.user: + name: ANSIBLE_TEST_2_1 + membership: [] + register: opn18 + failed_when: > + opn18.failed or + not opn18.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.user: + name: ANSIBLE_TEST_2_1 + membership: [] + register: opn19 + failed_when: > + opn19.failed or + opn19.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.user: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_2_1' + when: not ansible_check_mode + + - name: Cleanup Dummy Group + oxlorg.opnsense.group: + name: '{{ item }}' + state: 'absent' + loop: + - ANSIBLE_TEST_2_1 + - ANSIBLE_TEST_2_2 + diff: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wazuh_agent.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wazuh_agent.yml new file mode 100644 index 0000000..b74c48e --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wazuh_agent.yml @@ -0,0 +1,124 @@ +--- + +- name: 'Testing Wazuh Agent' + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'wazuh_agent' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid server address + oxlorg.opnsense.wazuh_agent: + server_address: 'invalid..server' + reload: false + register: opn_fail1 + failed_when: not opn_fail1.failed + when: not ansible_check_mode + + - name: Configuring - failing because of invalid port + oxlorg.opnsense.wazuh_agent: + server_address: '192.168.1.100' + port: 99999 + reload: false + register: opn_fail2 + failed_when: not opn_fail2.failed + when: not ansible_check_mode + + - name: Configuring - failing because of invalid debug level + oxlorg.opnsense.wazuh_agent: + server_address: '192.168.1.100' + debug_level: 5 + reload: false + register: opn_fail3 + failed_when: not opn_fail3.failed + when: not ansible_check_mode + + - name: Configuring + oxlorg.opnsense.wazuh_agent: + server_address: '192.168.1.100' + agent_name: 'test-agent' + protocol: 'tcp' + port: 1514 + debug_level: 1 + reload: false + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Configuring - nothing changed + oxlorg.opnsense.wazuh_agent: + server_address: '192.168.1.100' + agent_name: 'test-agent' + protocol: 'tcp' + port: 1514 + debug_level: 1 + reload: false + register: opn2 + failed_when: > + opn2.failed or + opn2.changed + when: not ansible_check_mode + + - name: Configuring - changing server + oxlorg.opnsense.wazuh_agent: + server_address: '10.0.0.100' + agent_name: 'test-agent-2' + protocol: 'udp' + port: 1515 + debug_level: 0 + enabled: false + reload: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Configuring - with authentication + oxlorg.opnsense.wazuh_agent: + server_address: '10.0.0.100' + agent_name: 'test-agent-auth' + protocol: 'tcp' + auth_password: 'test123' + auth_port: 1515 + remote_commands: true + syslog_programs: + - 'filterlog' + - 'suricata' + suricata_eve_log: true + rootcheck_enabled: false + syscollector_enabled: true + syscheck_enabled: false + active_response_enabled: true + active_response_remote_commands: false + # active_response_fw_alias_ignore: + # - 'trusted_hosts' + reload: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.wazuh_agent: + server_address: '127.0.0.1' + enabled: false + reload: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_acl.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_acl.yml new file mode 100644 index 0000000..778d123 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_acl.yml @@ -0,0 +1,78 @@ +--- + +- name: WebProxy Forward-ACL Settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_acl' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring + oxlorg.opnsense.webproxy_acl: + allow: ['192.168.0.0/24', '172.16.0.5'] + exclude: ['192.168.2.0/28', '172.16.1.5'] + banned: ['172.16.3.0/24'] + exclude_domains: ['oxlorg.net'] + block_domains: ['oxlorg.com'] + block_user_agents: ['test1'] + block_mime_types: ['video/flv'] + ports_tcp: ['80:http', '21:ftp'] + ports_ssl: ['443:https', '5433:random'] + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_acl: + allow: ['192.168.0.0/24', '172.16.1.0/29', '172.16.0.5'] + exclude: ['192.168.2.0/28', '172.16.1.5'] + banned: ['172.16.3.0/24', '172.16.2.5'] + exclude_domains: ['oxlorg.net'] + block_domains: ['oxlorg.com'] + block_user_agents: ['test1', 'test2'] + block_mime_types: ['video/flv', 'test'] + ports_tcp: ['80:http', '21:ftp'] + ports_ssl: ['443:https', '8443:random'] + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_acl: + allow: ['192.168.0.0/24', '172.16.1.0/29', '172.16.0.5'] + exclude: ['192.168.2.0/28', '172.16.1.5'] + banned: ['172.16.3.0/24', '172.16.2.5'] + exclude_domains: ['oxlorg.net'] + block_domains: ['oxlorg.com'] + block_user_agents: ['test1', 'test2'] + block_mime_types: ['video/flv', 'test'] + ports_tcp: ['80:http', '21:ftp'] + ports_ssl: ['443:https', '8443:random'] + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_acl: + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_auth.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_auth.yml new file mode 100644 index 0000000..6f8ef14 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_auth.yml @@ -0,0 +1,63 @@ +--- + +- name: WebProxy Forward-Auth Settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_auth' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring + oxlorg.opnsense.webproxy_auth: + method: 'Local Database' + prompt: 'Ansible Test' + ttl_h: 4 + processes: 8 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_auth: + method: 'Local Database' + prompt: 'Ansible Test NEW' + ttl_h: 3 + processes: 6 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_auth: + method: 'Local Database' + prompt: 'Ansible Test NEW' + ttl_h: 3 + processes: 6 + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_auth: + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_cache.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_cache.yml new file mode 100644 index 0000000..0972bc1 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_cache.yml @@ -0,0 +1,71 @@ +--- + +- name: WebProxy Cache Settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_cache' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid max-size + oxlorg.opnsense.webproxy_parent: + size_mb_max: 1000000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring + oxlorg.opnsense.webproxy_cache: + memory_mb: 512 + size_mb: 2048 + memory_cache_mode: 'always' + cache_windows_updates: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_cache: + memory_mb: 256 + size_mb: 1024 + memory_cache_mode: 'always' + cache_linux_packages: true + cache_windows_updates: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_cache: + memory_mb: 256 + size_mb: 1024 + memory_cache_mode: 'always' + cache_linux_packages: true + cache_windows_updates: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_cache: + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_forward.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_forward.yml new file mode 100644 index 0000000..4c8b504 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_forward.yml @@ -0,0 +1,89 @@ +--- + +- name: WebProxy Forward Settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_forward' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid cache-size + oxlorg.opnsense.webproxy_forward: + ssl_cache_mb: 70000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring + oxlorg.opnsense.webproxy_forward: + interfaces: ['lan', 'opt1'] + interfaces_ftp: ['lan'] + port: 3228 + port_ssl: 3229 + ssl_inspection: false + ssl_cache_mb: 30 + ssl_workers: 10 + transparent_ftp: true + snmp: true + snmp_password: 'test' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_forward: + interfaces: ['lan', 'opt1'] + interfaces_ftp: ['lan'] + port: 3238 + port_ssl: 3239 + ssl_inspection: false + ssl_cache_mb: 34 + ssl_workers: 10 + transparent_ftp: false + transparent: true + snmp: true + snmp_password: 'test' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_forward: + interfaces: ['lan', 'opt1'] + interfaces_ftp: ['lan'] + port: 3238 + port_ssl: 3239 + ssl_inspection: false + ssl_cache_mb: 34 + ssl_workers: 10 + transparent_ftp: false + transparent: true + snmp: true + snmp_password: 'test' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_forward: + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_general.yml new file mode 100644 index 0000000..7f1b8c4 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_general.yml @@ -0,0 +1,120 @@ +--- + +- name: WebProxy General Settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_general' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid connect_timeout + oxlorg.opnsense.webproxy_general: + connect_timeout: 200 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring + oxlorg.opnsense.webproxy_general: + errors: 'custom' + icp_port: 50000 + log: true + log_store: true + log_target: 'file_json' + dns_servers: ['192.168.0.1', '192.168.10.1'] + handling_forwarded_for: 'truncate' + suppress_version: true + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_general: + errors: 'squid' + icp_port: 30000 + log: true + log_store: true + log_target: 'syslog' + log_ignore: ['192.168.50.0/24'] + email: 'netadmin@oxlorg.net' + handling_forwarded_for: 'truncate' + suppress_version: false + register: opn9 + failed_when: > + opn9.failed or + not opn9.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.webproxy_general: + errors: 'squid' + icp_port: 30000 + log: true + log_store: true + log_target: 'syslog' + log_ignore: ['192.168.50.0/24'] + email: 'netadmin@oxlorg.net' + handling_forwarded_for: 'truncate' + suppress_version: false + enabled: false + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.webproxy_general: + errors: 'squid' + icp_port: 30000 + log: true + log_store: true + log_target: 'syslog' + log_ignore: ['192.168.50.0/24'] + email: 'netadmin@oxlorg.net' + handling_forwarded_for: 'truncate' + suppress_version: false + enabled: false + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.webproxy_general: + errors: 'squid' + icp_port: 30000 + log: true + log_store: true + log_target: 'syslog' + log_ignore: ['192.168.50.0/24'] + email: 'netadmin@oxlorg.net' + handling_forwarded_for: 'truncate' + suppress_version: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_general: + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_icap.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_icap.yml new file mode 100644 index 0000000..943aa98 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_icap.yml @@ -0,0 +1,126 @@ +--- + +- name: WebProxy Forward-ICAP Settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_icap' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid user-header + oxlorg.opnsense.webproxy_parent: + header_username: '!INVALID-' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring + oxlorg.opnsense.webproxy_icap: + response_url: 'icap://av.lan:1344/avscan' + ttl: 70 + send_username: true + send_client_ip: true + encode_username: true + preview_size: 3072 + exclude: ['oxlorg.net'] + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_icap: + response_url: 'icap://av.lan:1345/avscan' + ttl: 50 + send_username: false + send_client_ip: true + encode_username: true + preview_size: 2048 + exclude: ['oxlorg.net', 'random.site'] + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_icap: + response_url: 'icap://av.lan:1345/avscan' + ttl: 50 + send_username: false + send_client_ip: true + encode_username: true + preview_size: 2048 + exclude: ['oxlorg.net', 'random.site'] + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.webproxy_icap: + response_url: 'icap://av.lan:1345/avscan' + ttl: 50 + send_username: false + send_client_ip: true + encode_username: true + preview_size: 2048 + exclude: ['oxlorg.net', 'random.site'] + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.webproxy_icap: + response_url: 'icap://av.lan:1345/avscan' + ttl: 50 + send_username: false + send_client_ip: true + encode_username: true + preview_size: 2048 + exclude: ['oxlorg.net', 'random.site'] + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.webproxy_icap: + response_url: 'icap://av.lan:1345/avscan' + ttl: 50 + send_username: false + send_client_ip: true + encode_username: true + preview_size: 2048 + exclude: ['oxlorg.net', 'random.site'] + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_icap: + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_match.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_match.yml new file mode 100644 index 0000000..6f3fc18 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_match.yml @@ -0,0 +1,163 @@ +--- + +- name: WebProxy PAC-Match + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_pac_match' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Adding 1 - failing because of missing match arguments + oxlorg.opnsense.webproxy_remote_acl: + name: 'ANSIBLE_TEST_1_1' + type: "{{ item.t }}" + domain_level_from: "{{ item.df | default(omit) }}" + domain_level_to: "{{ item.dt | default(omit) }}" + hour_from: "{{ item.hf | default(omit) }}" + hour_to: "{{ item.ht | default(omit) }}" + month_from: "{{ item.mf | default(omit) }}" + month_to: "{{ item.mt | default(omit) }}" + weekday_from: "{{ item.wf | default(omit) }}" + weekday_to: "{{ item.wt | default(omit) }}" + register: opn_fail1 + failed_when: not opn_fail1.failed + loop: + - {'t': 'url_matches'} # no url + - {'t': 'hostname_matches'} # no hostname + - {'t': 'dns_domain_is'} # no hostname + - {'t': 'plain_hostname'} # no hostname + - {'t': 'is_resolvable'} # no hostname + - {'t': 'destination_in_net'} # no network + - {'t': 'my_ip_in_net'} # no network + - {'t': 'dns_domain_levels'} # no levels + - {'t': 'dns_domain_levels', 'dt': 3} # no from level + - {'t': 'dns_domain_levels', 'df': 1} # no to level + - {'t': 'weekday_range'} # no weekdays + - {'t': 'weekday_range', 'wt': 3} # no from weekday + - {'t': 'weekday_range', 'wf': 1} # no to weekday + - {'t': 'date_range'} # no month + - {'t': 'date_range', 'mt': 3} # no from month + - {'t': 'date_range', 'mf': 1} # no to month + - {'t': 'time_range'} # no hours + - {'t': 'time_range', 'ht': 3} # no from hours + - {'t': 'time_range', 'hf': 1} # no to hours + + - name: Adding 1 + oxlorg.opnsense.webproxy_pac_match: + name: 'ANSIBLE_TEST_1_1' + description: 'test' + negate: false + url: 'oxlorg.net/*' + type: 'url_matches' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_pac_match: + name: 'ANSIBLE_TEST_1_1' + hostname: 'test.oxlorg.net' + description: 'test_new' + type: 'hostname_matches' + negate: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_pac_match: + name: 'ANSIBLE_TEST_1_1' + hostname: 'test.oxlorg.net' + description: 'test_new' + type: 'hostname_matches' + negate: true + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.webproxy_pac_match: + name: 'ANSIBLE_TEST_1_2' + description: 'working hours' + type: 'time_range' + hour_from: 6 + hour_to: 18 + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + opn7.failed or + opn7.data | length != 2 + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.webproxy_pac_match: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + opn10.failed or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_pac_match: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn9 + failed_when: > + opn9.failed or + opn9.data | length != 0 + when: not ansible_check_mode + + - name: Removing non-existent + oxlorg.opnsense.webproxy_pac_match: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn11 + failed_when: > + opn11.failed or + opn11.changed diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_proxy.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_proxy.yml new file mode 100644 index 0000000..c34caea --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_proxy.yml @@ -0,0 +1,126 @@ +--- + +- name: WebProxy PAC-Proxy + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_pac_proxy' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Adding 1 - failing because of missing URL + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_1_1' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_1_1' + url: 'test.lan:3128' + description: 'test' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_1_1' + url: 'test.lan:4228' + description: 'test_new' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_1_1' + url: 'test.lan:4228' + description: 'test_new' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_1_2' + url: 'test.lan:4228' + description: 'test' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + opn7.failed or + opn7.data | length != 2 + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + opn10.failed or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_pac_proxy: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn9 + failed_when: > + opn9.failed or + opn9.data | length != 0 + when: not ansible_check_mode + + - name: Removing non-existent + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn11 + failed_when: > + opn11.failed or + opn11.changed diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_rule.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_rule.yml new file mode 100644 index 0000000..ae7bd74 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_pac_rule.yml @@ -0,0 +1,171 @@ +--- + +- name: WebProxy PAC-Rule + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_pac_rule' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Adding dummy match + oxlorg.opnsense.webproxy_pac_match: + name: 'ANSIBLE_TEST_2_1' + url: 'oxlorg.net/*' + type: 'url_matches' + when: not ansible_check_mode + + - name: Adding dummy proxy + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_2_1' + url: 'test.lan:3128' + description: 'test' + when: not ansible_check_mode + + - name: Adding 1 - failing because of missing matches + oxlorg.opnsense.webproxy_remote_acl: + description: 'ANSIBLE_TEST_1_1' + matches: [] + proxies: ['ANSIBLE_TEST_2_1'] + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of missing proxies + oxlorg.opnsense.webproxy_remote_acl: + description: 'ANSIBLE_TEST_1_1' + matches: ['ANSIBLE_TEST_2_1'] + proxies: [] + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + oxlorg.opnsense.webproxy_pac_rule: + description: 'ANSIBLE_TEST_1_1' + matches: ['ANSIBLE_TEST_2_1'] + proxies: ['ANSIBLE_TEST_2_1'] + join_type: 'and' + match_type: 'if' + when: not ansible_check_mode + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_pac_rule: + description: 'ANSIBLE_TEST_1_1' + matches: ['ANSIBLE_TEST_2_1'] + proxies: ['ANSIBLE_TEST_2_1'] + join_type: 'or' + match_type: 'unless' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_pac_rule: + description: 'ANSIBLE_TEST_1_1' + matches: ['ANSIBLE_TEST_2_1'] + proxies: ['ANSIBLE_TEST_2_1'] + join_type: 'or' + match_type: 'unless' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.webproxy_pac_rule: + description: 'ANSIBLE_TEST_1_2' + matches: ['ANSIBLE_TEST_2_1'] + proxies: ['ANSIBLE_TEST_2_1'] + join_type: 'and' + match_type: 'if' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + opn7.failed or + opn7.data | length != 2 + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.webproxy_pac_rule: + description: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + opn10.failed or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_pac_rule: + description: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn9 + failed_when: > + opn9.failed or + opn9.data | length != 0 + when: not ansible_check_mode + + - name: Cleanup match + oxlorg.opnsense.webproxy_pac_match: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup proxy + oxlorg.opnsense.webproxy_pac_proxy: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Removing non-existent + oxlorg.opnsense.webproxy_pac_rule: + description: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn11 + failed_when: > + opn11.failed or + opn11.changed diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_parent.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_parent.yml new file mode 100644 index 0000000..9d20eca --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_parent.yml @@ -0,0 +1,127 @@ +--- + +- name: WebProxy ProxyParent Settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_parent' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of missing host + oxlorg.opnsense.webproxy_parent: + enabled: true + port: 1000 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of missing port + oxlorg.opnsense.webproxy_parent: + enabled: true + host: '192.168.0.1' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring - failing because of invalid host + oxlorg.opnsense.webproxy_parent: + enabled: true + host: '192.168.0.1000' + port: 1000 + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Configuring + oxlorg.opnsense.webproxy_parent: + auth: true + host: '192.168.0.1' + port: 1000 + user: 'test' + password: 'secret' + local_ips: ['192.168.10.0/24', '192.168.2.0/29'] + local_domains: ['oxlorg.lan'] + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_parent: + auth: true + host: '192.168.0.10' + port: 1500 + user: 'test' + password: 'secret_new' + local_ips: ['192.168.10.0/24', '192.168.3.0/29'] + local_domains: ['oxlorg.lan'] + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.webproxy_parent: + auth: true + host: '192.168.0.10' + port: 1500 + user: 'test' + password: 'secret_new' + local_ips: ['192.168.10.0/24', '192.168.3.0/29'] + local_domains: ['oxlorg.lan'] + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.webproxy_parent: + auth: true + host: '192.168.0.10' + port: 1500 + user: 'test' + password: 'secret_new' + local_ips: ['192.168.10.0/24', '192.168.3.0/29'] + local_domains: ['oxlorg.lan'] + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.webproxy_parent: + auth: true + host: '192.168.0.10' + port: 1500 + user: 'test' + password: 'secret_new' + local_ips: ['192.168.10.0/24', '192.168.3.0/29'] + local_domains: ['oxlorg.lan'] + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_parent: + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_remote_acl.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_remote_acl.yml new file mode 100644 index 0000000..5ff062d --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_remote_acl.yml @@ -0,0 +1,183 @@ +--- + +- name: WebProxy Remote ACLs + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_remote_acl' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Adding 1 - failing because of missing URL + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST1' + description: 'test' + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 - failing because of incomplete credentials + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST1' + url: 'https://test.lan/rac1' + username: 'random' + description: 'test' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 - failing because of missing description + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST1' + url: 'https://test.lan/rac1' + username: 'random' + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Adding 1 - failing because of invalid file-name + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLE_TEST_1' + url: 'https://test.lan/rac1' + username: 'random' + description: 'test' + register: opn_fail4 + failed_when: not opn_fail4.failed + + - name: Adding 1 + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST1' + url: 'https://test.lan/rac1' + username: 'random' + password: 'random' + verify_ssl: true + description: 'test' + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST1' + url: 'https://test.lan/rac2' + username: 'random' + password: 'random2' + verify_ssl: false + description: 'test_new' + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Changing 1 - nothing changed + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST1' + url: 'https://test.lan/rac2' + username: 'random' + password: 'random2' + verify_ssl: false + description: 'test_new' + register: opn3 + failed_when: > + opn3.failed or + opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST1' + url: 'https://test.lan/rac2' + username: 'random' + password: 'random2' + verify_ssl: false + description: 'test_new' + enabled: false + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST1' + url: 'https://test.lan/rac2' + username: 'random' + password: 'random2' + verify_ssl: false + description: 'test_new' + enabled: false + register: opn5 + failed_when: > + opn5.failed or + opn5.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST2' + url: 'https://test.lan/rac2' + description: 'test' + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn7 + failed_when: > + opn7.failed or + opn7.data | length != 2 + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.webproxy_remote_acl: + file: 'ANSIBLETEST2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + opn10.failed or + opn10.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_remote_acl: + file: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLETEST1' + - 'ANSIBLETEST2' + when: not ansible_check_mode + + - name: Checking cleanup + oxlorg.opnsense.list: + register: opn9 + failed_when: > + opn9.failed or + opn9.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_traffic.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_traffic.yml new file mode 100644 index 0000000..e78f564 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/webproxy_traffic.yml @@ -0,0 +1,90 @@ +--- + +- name: WebProxy Traffic Settings + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'webproxy_traffic' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring + oxlorg.opnsense.webproxy_traffic: + download_kb_max: 40960 + upload_kb_max: 10240 + throttle_kb_bandwidth: 81920 + throttle_kb_host_bandwidth: 10240 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + oxlorg.opnsense.webproxy_traffic: + download_kb_max: 20480 + upload_kb_max: 5120 + throttle_kb_bandwidth: 40960 + throttle_kb_host_bandwidth: 5120 + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Disabling 1 + oxlorg.opnsense.webproxy_traffic: + download_kb_max: 20480 + upload_kb_max: 5120 + throttle_kb_bandwidth: 40960 + throttle_kb_host_bandwidth: 5120 + enabled: false + register: opn3 + failed_when: > + opn3.failed or + not opn3.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.webproxy_traffic: + download_kb_max: 20480 + upload_kb_max: 5120 + throttle_kb_bandwidth: 40960 + throttle_kb_host_bandwidth: 5120 + enabled: false + register: opn4 + failed_when: > + opn4.failed or + opn4.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.webproxy_traffic: + download_kb_max: 20480 + upload_kb_max: 5120 + throttle_kb_bandwidth: 40960 + throttle_kb_host_bandwidth: 5120 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.webproxy_traffic: + enabled: false + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_general.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_general.yml new file mode 100644 index 0000000..1334356 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_general.yml @@ -0,0 +1,34 @@ +--- + +- name: Testing WireGuard general + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.wireguard_general: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Disabling service - nothing to do + oxlorg.opnsense.wireguard_general: + enabled: false + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Enabling service + oxlorg.opnsense.wireguard_general: + enabled: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + + - name: Cleanup + oxlorg.opnsense.wireguard_general: + enabled: false diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_peer.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_peer.yml new file mode 100644 index 0000000..471c532 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_peer.yml @@ -0,0 +1,276 @@ +--- + +- name: Testing WireGuard peers + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.wireguard_peer: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + target: 'wireguard_peer' + + vars: + test: + pub1: 'Imk0UJX1clYYXKaDW9OdLE6J2N+X6aJ+/MxSlsDLW04=' + pub2: '7APwSdbet/8RQo7MyU95KdOPJ8YPD1ZaOh6LHNO5Cgw=' + psk: 'Jvhlj0xw67SF0e1n/xdpzqGskUH0trTAmNhDbpUodRI=' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of invalid allowed-ip + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + public_key: "{{ test.pub1 }}" + allowed_ips: ['192.168.0.1000'] + register: opn11 + failed_when: not opn11.failed + + - name: Adding 1 - failing because of invalid port + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + public_key: "{{ test.pub1 }}" + allowed_ips: ['192.168.0.1/32'] + port: 70000 + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 - failing because of missing public key + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1/32'] + register: opn14 + failed_when: not opn14.failed + + - name: Adding 1 - failing because of missing allowed-ips + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + register: opn15 + failed_when: not opn15.failed + + - name: Adding 1 + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + target: 'wg.test.oxlorg.net' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Disabling 1 + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + target: 'wg.test.oxlorg.net' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + target: 'wg.test.oxlorg.net' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + enabled: false + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + target: 'wg.test.oxlorg.net' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_2' + target: 'wg2.test.oxlorg.net' + allowed_ips: ['192.168.1.1/32'] + public_key: "{{ test.pub2 }}" + psk: "{{ test.psk }}" + keepalive: 5 + port: 51899 + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_2' + target: 'wg2.test.oxlorg.net' + allowed_ips: ['192.168.1.1/32'] + public_key: "{{ test.pub2 }}" + psk: "{{ test.psk }}" + keepalive: 5 + port: 51899 + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_2' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.wireguard_peer: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode + +- name: Testing WireGuard peers - linking to instance/server + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + vars: + test: + pub1: 'Imk0UJX1clYYXKaDW9OdLE6J2N+X6aJ+/MxSlsDLW04=' + priv1: '2ESImYf/PhpEqgK2s/5med7CbvyZrUNpsQCaKrpsW0g=' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Adding dummy instance + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_2_1' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + link_peers: false + when: not ansible_check_mode + + - name: Adding + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_3_1' + target: 'wg.test.oxlorg.net' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + servers: 'ANSIBLE_TEST_2_1' + register: opn16 + failed_when: > + opn16.failed or + not opn16.changed + when: not ansible_check_mode + + - name: Nothing changed + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_3_1' + target: 'wg.test.oxlorg.net' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + servers: 'ANSIBLE_TEST_2_1' + register: opn17 + failed_when: > + opn17.failed or + opn17.changed + when: not ansible_check_mode + + - name: Server - Nothing changed + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_2_1' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + link_peers: false + register: opn19 + failed_when: > + opn19.failed or + opn19.changed + when: not ansible_check_mode + + - name: Peer - Nothing changed + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_3_1' + target: 'wg.test.oxlorg.net' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + servers: 'ANSIBLE_TEST_2_1' + register: opn18 + failed_when: > + opn18.failed or + opn18.changed + when: not ansible_check_mode + + - name: Cleanup peer + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_3_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup dummy instance + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_server.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_server.yml new file mode 100644 index 0000000..9938e46 --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_server.yml @@ -0,0 +1,335 @@ +--- + +- name: Testing WireGuard servers + hosts: localhost + gather_facts: no + module_defaults: + group/oxlorg.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + oxlorg.opnsense.list: + target: 'wireguard_server' + + vars: + test: + pub1: 'Imk0UJX1clYYXKaDW9OdLE6J2N+X6aJ+/MxSlsDLW04=' + pub2: '7eWtiJ2nRNdLjkJgGp9/Cac3LylHKJ//D5AuNnkzxxI=' + pub3: '7APwSdbet/8RQo7MyU95KdOPJ8YPD1ZaOh6LHNO5Cgw=' + priv1: '2ESImYf/PhpEqgK2s/5med7CbvyZrUNpsQCaKrpsW0g=' + priv2: 'OIra/s01yynMZcIk1SL+Hk3ucyBdpnT9dd0WUxnW9GI=' + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Listing + oxlorg.opnsense.list: + register: opn10 + failed_when: > + 'data' not in opn10 or + opn10.data | length != 0 + + - name: Removing - does not exist + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + state: 'absent' + register: opn1 + failed_when: > + opn1.failed or + opn1.changed + + - name: Adding 1 - failing because of invalid tunnel ip + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1000'] + register: opn11 + failed_when: not opn11.failed + + - name: Adding 1 - failing because of invalid port + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + port: 70000 + allowed_ips: ['192.168.0.1'] + register: opn12 + failed_when: not opn12.failed + + - name: Adding 1 - failing because of invalid dns-server + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1'] + dns_servers: ['192.168.0.1000'] + register: opn14 + failed_when: not opn14.failed + + - name: Adding 1 - failing because of missing allowed-ips + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_1_1' + register: opn17 + failed_when: not opn17.failed + + - name: Adding 1 - failing because of invalid gateway + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1/32'] + gateway: '192.168.0.1000' + register: opn18 + failed_when: not opn18.failed + + - name: Adding 1 - failing because of missing pk + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + register: opn19 + failed_when: not opn19.failed + + - name: Adding 1 - failing because of non-existent CARP-VIP + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1/32'] + private_key: "{{ test.priv1 }}" + public_key: "{{ test.pub1 }}" + vip: '192.168.3.1' + register: opn24 + failed_when: not opn24.failed + + - name: Adding 1 + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + register: opn4 + failed_when: > + opn4.failed or + not opn4.changed + + - name: Disabling 1 + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + enabled: false + register: opn6 + failed_when: > + opn6.failed or + not opn6.changed + when: not ansible_check_mode + + - name: Disabling 1 - nothing changed + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + enabled: false + register: opn9 + failed_when: > + opn9.failed or + opn9.changed + when: not ansible_check_mode + + - name: Enabling 1 + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_1' + allowed_ips: ['192.168.0.1/32'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + register: opn7 + failed_when: > + opn7.failed or + not opn7.changed + when: not ansible_check_mode + + - name: Adding 2 + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_2' + allowed_ips: ['192.168.1.1/32', '2a0a:e5c0::1/64'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + port: 51999 + mtu: 1400 + dns_servers: ['1.1.1.1'] + disable_routes: true + gateway: '192.168.100.1' + register: opn5 + failed_when: > + opn5.failed or + not opn5.changed + + - name: Adding 2 - nothing changed + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_2' + allowed_ips: ['192.168.1.1/32', '2a0a:e5c0::1/64'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + port: 51999 + mtu: 1400 + dns_servers: ['1.1.1.1'] + disable_routes: true + gateway: '192.168.100.1' + register: opn13 + failed_when: > + opn13.failed or + opn13.changed + when: not ansible_check_mode + + - name: Removing 2 + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_2' + allowed_ips: ['192.168.1.1/32', '2a0a:e5c0::1/64'] + public_key: "{{ test.pub1 }}" + private_key: "{{ test.priv1 }}" + port: 51999 + mtu: 1400 + dns_servers: ['1.1.1.1'] + disable_routes: true + gateway: '192.168.100.1' + state: 'absent' + register: opn8 + failed_when: > + opn8.failed or + not opn8.changed + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn3 + failed_when: > + 'data' not in opn3 or + opn3.data | length != 1 + when: not ansible_check_mode + + - name: Adding dummy peer + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_2_1' + target: 'wg.test.oxlorg.net' + allowed_ips: ['192.168.3.2/32'] + public_key: "{{ test.pub3 }}" + when: not ansible_check_mode + + - name: Adding 3 + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_3' + allowed_ips: ['192.168.3.1/32'] + public_key: "{{ test.pub2 }}" + private_key: "{{ test.priv2 }}" + peers: ['ANSIBLE_TEST_2_1'] + register: opn15 + failed_when: > + opn15.failed or + not opn15.changed + when: not ansible_check_mode + + - name: Adding 3 - nothing changed + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_3' + allowed_ips: ['192.168.3.1/32'] + public_key: "{{ test.pub2 }}" + private_key: "{{ test.priv2 }}" + peers: ['ANSIBLE_TEST_2_1'] + register: opn16 + failed_when: > + opn16.failed or + opn16.changed + when: not ansible_check_mode + + - name: Adding dummy CARP-VIP + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '192.168.2.1/24' + mode: 'carp' + vhid: 10 + password: 'top_secret' + when: not ansible_check_mode + + - name: Adding 4 - linked to CARP-VIP + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_4' + allowed_ips: ['192.168.3.1/32'] + public_key: "{{ test.pub2 }}" + private_key: "{{ test.priv2 }}" + peers: ['ANSIBLE_TEST_2_1'] + vip: '192.168.2.1' + register: opn21 + failed_when: > + opn21.failed or + not opn21.changed + when: not ansible_check_mode + + - name: Adding 4 - nothing changed + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_4' + allowed_ips: ['192.168.3.1/32'] + public_key: "{{ test.pub2 }}" + private_key: "{{ test.priv2 }}" + peers: ['ANSIBLE_TEST_2_1'] + vip: '192.168.2.1' + register: opn22 + failed_when: > + opn22.failed or + opn22.changed + when: not ansible_check_mode + + - name: Changing 4 - do not link peers + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_4' + allowed_ips: ['192.168.3.1/32'] + public_key: "{{ test.pub2 }}" + private_key: "{{ test.priv2 }}" + vip: '192.168.2.1' + link_peers: false + register: opn23 + failed_when: > + opn23.failed or + opn23.changed + when: not ansible_check_mode + + - name: Adding 4 - nothing changed + oxlorg.opnsense.wireguard_server: + name: 'ANSIBLE_TEST_1_4' + allowed_ips: ['192.168.3.1/32'] + public_key: "{{ test.pub2 }}" + private_key: "{{ test.priv2 }}" + peers: ['ANSIBLE_TEST_2_1'] + vip: '192.168.2.1' + register: opn24 + failed_when: > + opn24.failed or + opn24.changed + when: not ansible_check_mode + + - name: Cleanup + oxlorg.opnsense.wireguard_server: + name: "{{ item }}" + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + - 'ANSIBLE_TEST_1_4' + when: not ansible_check_mode + + - name: Cleanup peer + oxlorg.opnsense.wireguard_peer: + name: 'ANSIBLE_TEST_2_1' + state: 'absent' + when: not ansible_check_mode + + - name: Cleanup VIP + oxlorg.opnsense.interface_vip: + interface: 'opt1' + address: '192.168.2.1/24' + state: 'absent' + when: not ansible_check_mode + + - name: Listing + oxlorg.opnsense.list: + register: opn2 + failed_when: > + 'data' not in opn2 or + opn2.data | length != 0 + when: not ansible_check_mode diff --git a/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_show.yml b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_show.yml new file mode 100644 index 0000000..89c4f6c --- /dev/null +++ b/ansible/playbooks/collections/ansible_collections/oxlorg/opnsense/tests/wireguard_show.yml @@ -0,0 +1,21 @@ +--- + +- name: Testing WireGuard info querying + hosts: localhost + gather_facts: no + module_defaults: + oxlorg.opnsense.wireguard_show: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + environment: + HTTPS_PROXY: "{{ lookup('ansible.builtin.env', 'TEST_PROXY') | default('') }}" + + tasks: + - name: Showing config + oxlorg.opnsense.wireguard_show: + register: opn1 + failed_when: > + opn1.failed or + 'data' not in opn1 diff --git a/ansible/playbooks/opnsense.yaml b/ansible/playbooks/opnsense.yaml new file mode 100644 index 0000000..fd2f3e1 --- /dev/null +++ b/ansible/playbooks/opnsense.yaml @@ -0,0 +1,8 @@ +--- +# Usese oxlorg.opnsense +# Check documentation @ https://ansible-opnsense.oxl.app/usage/2_basic.html#prerequisites +- name: Configure OPNSense + hosts: opn + collections: + - oxlorg.opnsense +