From a5664e742ae8c9f64f3fe46cf12b17f931074ebf Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Thu, 2 Jan 2025 18:44:00 +0100 Subject: [PATCH 1/9] Implement modules for acmeclient plugin --- docs/source/modules/acmeclient.rst | 497 ++++++++++++++++++ meta/runtime.yml | 11 + plugins/module_utils/main/acme_account.py | 81 +++ plugins/module_utils/main/acme_action.py | 103 ++++ plugins/module_utils/main/acme_certificate.py | 131 +++++ plugins/module_utils/main/acme_general.py | 60 +++ plugins/module_utils/main/acme_validation.py | 131 +++++ plugins/modules/acme_account.py | 98 ++++ plugins/modules/acme_action.py | 281 ++++++++++ plugins/modules/acme_certificate.py | 115 ++++ plugins/modules/acme_general.py | 93 ++++ plugins/modules/acme_validation.py | 337 ++++++++++++ plugins/modules/list.py | 22 +- scripts/test.sh | 5 + tests/acme_account.yml | 132 +++++ tests/acme_action.yml | 398 ++++++++++++++ tests/acme_certificate.yml | 331 ++++++++++++ tests/acme_general.yml | 84 +++ tests/acme_validation.yml | 358 +++++++++++++ 19 files changed, 3267 insertions(+), 1 deletion(-) create mode 100644 docs/source/modules/acmeclient.rst create mode 100644 plugins/module_utils/main/acme_account.py create mode 100644 plugins/module_utils/main/acme_action.py create mode 100644 plugins/module_utils/main/acme_certificate.py create mode 100644 plugins/module_utils/main/acme_general.py create mode 100644 plugins/module_utils/main/acme_validation.py create mode 100644 plugins/modules/acme_account.py create mode 100644 plugins/modules/acme_action.py create mode 100644 plugins/modules/acme_certificate.py create mode 100644 plugins/modules/acme_general.py create mode 100644 plugins/modules/acme_validation.py create mode 100644 tests/acme_account.yml create mode 100644 tests/acme_action.yml create mode 100644 tests/acme_certificate.yml create mode 100644 tests/acme_general.yml create mode 100644 tests/acme_validation.yml diff --git a/docs/source/modules/acmeclient.rst b/docs/source/modules/acmeclient.rst new file mode 100644 index 0000000..61c15c3 --- /dev/null +++ b/docs/source/modules/acmeclient.rst @@ -0,0 +1,497 @@ +.. _modules_acmeclient: + +.. include:: ../_include/head.rst + +=========== +ACME Client +=========== + +**STATE**: unstable + +**TESTS**: `acme_certificate `_ + +**API Docs**: `Plugins - Acmeclient `_ + + +Contribution +************ + +Thanks to `@jiuka `_ for developing this module! + +Prerequisites +************* + +You need to install the FRR plugin: + +``` +os-acme-client +``` + +You can also install it using the package module. + +Definition +********** + +.. include:: ../_include/param_basic.rst + +ansibleguy.opnsense.acme_general +==================================== + +.. csv-table:: Definition + :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" + :widths: 15 10 10 10 10 45 + + "enable","boolean","false","false","\-","Enable ACME client plugin." + "auto_renewal","boolean","false","true","\-","Enable automatic renewal for certificates to prevent expiration." + "challenge_port","integer","false","43580","\-","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","integer","false","43581","\-","The service port when using TLS-ALPN-01 as challenge type. It works similar to the HTTP-01 challenge type." + "restart_timeout","integer","false","600","\-","The maximum time in seconds to wait for an automation to complete. When the timeout is reached the command is forcefully aborted." + "haproxy_integration","boolean","false","false","\-","Enable automatic integration with the OPNsense HAProxy plugin. **Requires that the OPNsense HAProxy plugin is installed.** This will automatically add the required backend, server, action and ACL for you. You just need to select your HAProxy frontend when configuring the certificate or challenge type." + "log_level","string","false","normal","\-","Specifies the log level for acme.sh. Other log levels then 'default' add 'information' are for debug purposes, but be aware that this will break the log formatting in the GUI. Levels 'debug2' and 'debug3' log successively deeper log messages from the acme.sh including messages from DNS-01 DNSAPI scripts. One of: 'normal', 'extended', 'debug', 'debug2', 'debug3'" + "show_intro","boolean","false","true","\-","Disable to hide all introduction pages." + "reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst + + +ansibleguy.opnsense.acme_account +================================ + +.. csv-table:: Definition + :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" + :widths: 15 10 10 10 10 45 + + "name","string","true","\-","\-","Name to identify this account." + "description","string","false","\-","desc","Description for this account." + "email","string","false","\-","\-","E-mail address for this account." + "ca","string","false","letsencrypt","\-","One of: 'buypass', 'buypass_test', 'google', 'google_test', 'letsencrypt', 'letsencrypt_test', 'sslcom', 'zerossl', 'custom'" + "custom_ca","string","false","","\-","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","string","false","\-","\-","An value provided by the CA when using ACME External Account Binding (EAB)." + "eab_hmac","string","false","\-","\-","An value provided by the CA when using ACME External Account Binding (EAB)." + "register","boolean","false","false","\-","Register the selected account with the configured ACME CA." + "reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst + + +ansibleguy.opnsense.acme_validation +=================================== + +.. csv-table:: Definition + :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" + :widths: 15 10 10 10 10 45 + + "name","string","true","\-","\-","Name to identify this validation." + "description","string","false","\-","desc","Description for this validation." + "method","string","false","dns01","\-","Set the ACME challenge type. One of: 'http01', 'dns01', 'tlsalpn01'" + "http_service","string","false","opnsense","\-","HTTP Service to use for 'http01' validation. One of: 'opnsense', 'haproxy'" + "http_opn_autodiscovery","boolean","false","true","\-","The FQDN's used in your certificate must currently point to an official IP address. Choose this option to let OPNsense try to auto-discover these IP addresses. This will lead to a short downtime of the service that is normally used with this IP address." + "http_opn_interface","string","false","\-","\-","The FQDN's used in your certificate must currently point to an official IP address. Choose the interface where this IP address is currently configured. OPNsense will automatically create a temporary port forward to allow the ACME validation to succeed. This will lead to a short downtime of the service that is normally used with this IP address." + "http_opn_ipaddresses","list","false","\-","\-","The FQDN's used in your certificate must currently point to one or more official IP addresses. Enter the all of these IP addresses here. OPNsense will automatically create a temporary port forward to allow the ACME validation to succeed. This will lead to a short downtime of the service that is normally used with these IP addresses." + "http_haproxy_inject","boolean","false","true","\-","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","list","false","\-","\-","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","boolean","false","true","\-","The FQDN's used in your certificate must currently point to an official IP address. Choose this option to let OPNsense try to auto-discover these IP addresses. This will lead to a short downtime of the service that is normally used with this IP address." + "tlsalpn_acme_interface","string","false","\-","\-","The FQDN's used in your certificate must currently point to an official IP address. Choose the interface where this IP address is currently configured. OPNsense will automatically create a temporary port forward to allow the ACME validation to succeed. This will lead to a short downtime of the service that is normally used with this IP address." + "tlsalpn_acme_ipaddresses","list","false","\-","\-","The FQDN's used in your certificate must currently point to one or more official IP addresses. Enter the all of these IP addresses here. OPNsense will automatically create a temporary port forward to allow the ACME validation to succeed. This will lead to a short downtime of the service that is normally used with these IP addresses." + "dns_service","string","false","dns_freedns","\-","DNS Service. One of: '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","integer","false","0","\-","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","string","false","\-","\-","Paramater token for dns_service 'dns_active24'" + "dns_ad_key","string","false","\-","\-","Paramater key for dns_service 'dns_ad'" + "dns_ali_key","string","false","\-","\-","Paramater key for dns_service 'dns_ali'" + "dns_ali_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_ali'" + "dns_autodns_user","string","false","\-","\-","Paramater user for dns_service 'dns_autodns'" + "dns_autodns_password","string","false","\-","\-","Paramater password for dns_service 'dns_autodns'" + "dns_autodns_context","string","false","\-","\-","Paramater context for dns_service 'dns_autodns'" + "dns_aws_id","string","false","\-","\-","Paramater id for dns_service 'dns_aws'" + "dns_aws_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_aws'" + "dns_azuredns_subscriptionid","string","false","\-","\-","Paramater subscriptionid for dns_service 'dns_azuredns'" + "dns_azuredns_tenantid","string","false","\-","\-","Paramater tenantid for dns_service 'dns_azuredns'" + "dns_azuredns_appid","string","false","\-","\-","Paramater appid for dns_service 'dns_azuredns'" + "dns_azuredns_clientsecret","string","false","\-","\-","Paramater clientsecret for dns_service 'dns_azuredns'" + "dns_bunny_api_key","string","false","\-","\-","Paramater api_key for dns_service 'dns_bunny'" + "dns_cf_email","string","false","\-","\-","Paramater email for dns_service 'dns_cf'" + "dns_cf_key","string","false","\-","\-","Paramater key for dns_service 'dns_cf'" + "dns_cf_token","string","false","\-","\-","Paramater token for dns_service 'dns_cf'" + "dns_cf_account_id","string","false","\-","\-","Paramater account_id for dns_service 'dns_cf'" + "dns_cf_zone_id","string","false","\-","\-","Paramater zone_id for dns_service 'dns_cf'" + "dns_cloudns_auth_id","string","false","\-","\-","Paramater auth_id for dns_service 'dns_cloudns'" + "dns_cloudns_sub_auth_id","string","false","\-","\-","Paramater sub_auth_id for dns_service 'dns_cloudns'" + "dns_cloudns_auth_password","string","false","\-","\-","Paramater auth_password for dns_service 'dns_cloudns'" + "dns_cx_key","string","false","\-","\-","Paramater key for dns_service 'dns_cx'" + "dns_cx_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_cx'" + "dns_cyon_user","string","false","\-","\-","Paramater user for dns_service 'dns_cyon'" + "dns_cyon_password","string","false","\-","\-","Paramater password for dns_service 'dns_cyon'" + "dns_da_key","string","false","\-","\-","Paramater key for dns_service 'dns_da'" + "dns_da_insecure","boolean","false","false","\-","Paramater insecure for dns_service 'dns_da'" + "dns_ddnss_token","string","false","\-","\-","Paramater token for dns_service 'dns_ddnss'" + "dns_dgon_key","string","false","\-","\-","Paramater key for dns_service 'dns_dgon'" + "dns_dnsexit_auth_user","string","false","\-","\-","Paramater auth_user for dns_service 'dns_dnsexit'" + "dns_dnsexit_auth_pass","string","false","\-","\-","Paramater auth_pass for dns_service 'dns_dnsexit'" + "dns_dnsexit_api","string","false","\-","\-","Paramater api for dns_service 'dns_dnsexit'" + "dns_dnshome_password","string","false","\-","\-","Paramater password for dns_service 'dns_dnshome'" + "dns_dnshome_subdomain","string","false","\-","\-","Paramater subdomain for dns_service 'dns_dnshome'" + "dns_dnsimple_token","string","false","\-","\-","Paramater token for dns_service 'dns_dnsimple'" + "dns_dnsservices_user","string","false","\-","\-","Paramater user for dns_service 'dns_dnsservices'" + "dns_dnsservices_password","string","false","\-","\-","Paramater password for dns_service 'dns_dnsservices'" + "dns_doapi_token","string","false","\-","\-","Paramater token for dns_service 'dns_doapi'" + "dns_do_pid","string","false","\-","\-","Paramater pid for dns_service 'dns_do'" + "dns_do_password","string","false","\-","\-","Paramater password for dns_service 'dns_do'" + "dns_domeneshop_token","string","false","\-","\-","Paramater token for dns_service 'dns_domeneshop'" + "dns_domeneshop_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_domeneshop'" + "dns_dp_id","string","false","\-","\-","Paramater id for dns_service 'dns_dp'" + "dns_dp_key","string","false","\-","\-","Paramater key for dns_service 'dns_dp'" + "dns_duckdns_token","string","false","\-","\-","Paramater token for dns_service 'dns_duckdns'" + "dns_dyn_customer","string","false","\-","\-","Paramater customer for dns_service 'dns_dyn'" + "dns_dyn_user","string","false","\-","\-","Paramater user for dns_service 'dns_dyn'" + "dns_dyn_password","string","false","\-","\-","Paramater password for dns_service 'dns_dyn'" + "dns_dynu_clientid","string","false","\-","\-","Paramater clientid for dns_service 'dns_dynu'" + "dns_dynu_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_dynu'" + "dns_freedns_user","string","false","\-","\-","Paramater user for dns_service 'dns_freedns'" + "dns_freedns_password","string","false","\-","\-","Paramater password for dns_service 'dns_freedns'" + "dns_fornex_api_key","string","false","\-","\-","Paramater api_key for dns_service 'dns_fornex'" + "dns_gandi_livedns_key","string","false","\-","\-","Paramater livedns_key for dns_service 'dns_gandi'" + "dns_gandi_livedns_token","string","false","\-","\-","Paramater livedns_token for dns_service 'dns_gandi'" + "dns_gcloud_key","string","false","\-","\-","Paramater key for dns_service 'dns_gcloud'" + "dns_googledomains_access_token","string","false","\-","\-","Paramater access_token for dns_service 'dns_googledomains'" + "dns_googledomains_zone","string","false","\-","\-","Paramater zone for dns_service 'dns_googledomains'" + "dns_gd_key","string","false","\-","\-","Paramater key for dns_service 'dns_gd'" + "dns_gd_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_gd'" + "dns_hostingde_server","string","false","\-","\-","Paramater server for dns_service 'dns_hostingde'" + "dns_hostingde_apiKey","string","false","\-","\-","Paramater apiKey for dns_service 'dns_hostingde'" + "dns_he_user","string","false","\-","\-","Paramater user for dns_service 'dns_he'" + "dns_he_password","string","false","\-","\-","Paramater password for dns_service 'dns_he'" + "dns_infoblox_credentials","string","false","\-","\-","Paramater credentials for dns_service 'dns_infoblox'" + "dns_infoblox_server","string","false","\-","\-","Paramater server for dns_service 'dns_infoblox'" + "dns_inwx_user","string","false","\-","\-","Paramater user for dns_service 'dns_inwx'" + "dns_inwx_password","string","false","\-","\-","Paramater password for dns_service 'dns_inwx'" + "dns_inwx_shared_secret","string","false","\-","\-","Paramater shared_secret for dns_service 'dns_inwx'" + "dns_ionos_prefix","string","false","\-","\-","Paramater prefix for dns_service 'dns_ionos'" + "dns_ionos_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_ionos'" + "dns_ipv64_token","string","false","\-","\-","Paramater token for dns_service 'dns_ipv64'" + "dns_ispconfig_user","string","false","\-","\-","Paramater user for dns_service 'dns_ispconfig'" + "dns_ispconfig_password","string","false","\-","\-","Paramater password for dns_service 'dns_ispconfig'" + "dns_ispconfig_api","string","false","\-","\-","Paramater api for dns_service 'dns_ispconfig'" + "dns_ispconfig_insecure","boolean","false","false","\-","Paramater insecure for dns_service 'dns_ispconfig'" + "dns_jd_id","string","false","\-","\-","Paramater id for dns_service 'dns_jd'" + "dns_jd_region","string","false","\-","\-","Paramater region for dns_service 'dns_jd'" + "dns_jd_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_jd'" + "dns_joker_username","string","false","\-","\-","Paramater username for dns_service 'dns_joker'" + "dns_joker_password","string","false","\-","\-","Paramater password for dns_service 'dns_joker'" + "dns_kinghost_username","string","false","\-","\-","Paramater username for dns_service 'dns_kinghost'" + "dns_kinghost_password","string","false","\-","\-","Paramater password for dns_service 'dns_kinghost'" + "dns_knot_server","string","false","\-","\-","Paramater server for dns_service 'dns_knot'" + "dns_knot_key","string","false","\-","\-","Paramater key for dns_service 'dns_knot'" + "dns_limacity_apikey","string","false","\-","\-","Paramater apikey for dns_service 'dns_limacity'" + "dns_linode_v4_key","string","false","\-","\-","Paramater v4_key for dns_service 'dns_linode'" + "dns_loopia_api","string","false","\-","\-","Paramater api for dns_service 'dns_loopia'" + "dns_loopia_user","string","false","\-","\-","Paramater user for dns_service 'dns_loopia'" + "dns_loopia_password","string","false","\-","\-","Paramater password for dns_service 'dns_loopia'" + "dns_lua_email","string","false","\-","\-","Paramater email for dns_service 'dns_lua'" + "dns_lua_key","string","false","\-","\-","Paramater key for dns_service 'dns_lua'" + "dns_miab_user","string","false","\-","\-","Paramater user for dns_service 'dns_miab'" + "dns_miab_password","string","false","\-","\-","Paramater password for dns_service 'dns_miab'" + "dns_miab_server","string","false","\-","\-","Paramater server for dns_service 'dns_miab'" + "dns_me_key","string","false","\-","\-","Paramater key for dns_service 'dns_me'" + "dns_me_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_me'" + "dns_mydnsjp_masterid","string","false","\-","\-","Paramater masterid for dns_service 'dns_mydnsjp'" + "dns_mydnsjp_password","string","false","\-","\-","Paramater password for dns_service 'dns_mydnsjp'" + "dns_mythic_beasts_key","string","false","\-","\-","Paramater beasts_key for dns_service 'dns_mythic'" + "dns_mythic_beasts_secret","string","false","\-","\-","Paramater beasts_secret for dns_service 'dns_mythic'" + "dns_namecheap_user","string","false","\-","\-","Paramater user for dns_service 'dns_namecheap'" + "dns_namecheap_api","string","false","\-","\-","Paramater api for dns_service 'dns_namecheap'" + "dns_namecheap_sourceip","string","false","\-","\-","Paramater sourceip for dns_service 'dns_namecheap'" + "dns_namecom_user","string","false","\-","\-","Paramater user for dns_service 'dns_namecom'" + "dns_namecom_token","string","false","\-","\-","Paramater token for dns_service 'dns_namecom'" + "dns_namesilo_key","string","false","\-","\-","Paramater key for dns_service 'dns_namesilo'" + "dns_nederhost_key","string","false","\-","\-","Paramater key for dns_service 'dns_nederhost'" + "dns_netcup_cid","string","false","\-","\-","Paramater cid for dns_service 'dns_netcup'" + "dns_netcup_key","string","false","\-","\-","Paramater key for dns_service 'dns_netcup'" + "dns_netcup_pw","string","false","\-","\-","Paramater pw for dns_service 'dns_netcup'" + "dns_njalla_token","string","false","\-","\-","Paramater token for dns_service 'dns_njalla'" + "dns_nsone_key","string","false","\-","\-","Paramater key for dns_service 'dns_nsone'" + "dns_nsupdate_server","string","false","\-","\-","Paramater server for dns_service 'dns_nsupdate'" + "dns_nsupdate_zone","string","false","\-","\-","Paramater zone for dns_service 'dns_nsupdate'" + "dns_nsupdate_key","string","false","\-","\-","Paramater key for dns_service 'dns_nsupdate'" + "dns_oci_cli_user","string","false","\-","\-","Paramater cli_user for dns_service 'dns_oci'" + "dns_oci_cli_tenancy","string","false","\-","\-","Paramater cli_tenancy for dns_service 'dns_oci'" + "dns_oci_cli_region","string","false","\-","\-","Paramater cli_region for dns_service 'dns_oci'" + "dns_oci_cli_key","string","false","\-","\-","Paramater cli_key for dns_service 'dns_oci'" + "dns_online_key","string","false","\-","\-","Paramater key for dns_service 'dns_online'" + "dns_opnsense_host","string","false","localhost","\-","Paramater host for dns_service 'dns_opnsense'" + "dns_opnsense_port","integer","false","443","\-","Paramater port for dns_service 'dns_opnsense'" + "dns_opnsense_key","string","false","\-","\-","Paramater key for dns_service 'dns_opnsense'" + "dns_opnsense_token","string","false","\-","\-","Paramater token for dns_service 'dns_opnsense'" + "dns_opnsense_insecure","boolean","false","false","\-","Paramater insecure for dns_service 'dns_opnsense'" + "dns_ovh_app_key","string","false","\-","\-","Paramater app_key for dns_service 'dns_ovh'" + "dns_ovh_app_secret","string","false","\-","\-","Paramater app_secret for dns_service 'dns_ovh'" + "dns_ovh_consumer_key","string","false","\-","\-","Paramater consumer_key for dns_service 'dns_ovh'" + "dns_ovh_endpoint","string","false","\-","\-","Paramater endpoint for dns_service 'dns_ovh'" + "dns_pleskxml_user","string","false","\-","\-","Paramater user for dns_service 'dns_pleskxml'" + "dns_pleskxml_pass","string","false","\-","\-","Paramater pass for dns_service 'dns_pleskxml'" + "dns_pleskxml_uri","string","false","\-","\-","Paramater uri for dns_service 'dns_pleskxml'" + "dns_pdns_url","string","false","\-","\-","Paramater url for dns_service 'dns_pdns'" + "dns_pdns_serverid","string","false","\-","\-","Paramater serverid for dns_service 'dns_pdns'" + "dns_pdns_token","string","false","\-","\-","Paramater token for dns_service 'dns_pdns'" + "dns_porkbun_key","string","false","\-","\-","Paramater key for dns_service 'dns_porkbun'" + "dns_porkbun_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_porkbun'" + "dns_sl_key","string","false","\-","\-","Paramater key for dns_service 'dns_sl'" + "dns_selfhost_user","string","false","\-","\-","Paramater user for dns_service 'dns_selfhost'" + "dns_selfhost_password","string","false","\-","\-","Paramater password for dns_service 'dns_selfhost'" + "dns_selfhost_map","string","false","\-","\-","Paramater map for dns_service 'dns_selfhost'" + "dns_servercow_username","string","false","\-","\-","Paramater username for dns_service 'dns_servercow'" + "dns_servercow_password","string","false","\-","\-","Paramater password for dns_service 'dns_servercow'" + "dns_simply_api_key","string","false","\-","\-","Paramater api_key for dns_service 'dns_simply'" + "dns_simply_account_name","string","false","\-","\-","Paramater account_name for dns_service 'dns_simply'" + "dns_transip_username","string","false","\-","\-","Paramater username for dns_service 'dns_transip'" + "dns_transip_key","string","false","\-","\-","Paramater key for dns_service 'dns_transip'" + "dns_udr_user","string","false","\-","\-","Paramater user for dns_service 'dns_udr'" + "dns_udr_password","string","false","\-","\-","Paramater password for dns_service 'dns_udr'" + "dns_uno_key","string","false","\-","\-","Paramater key for dns_service 'dns_uno'" + "dns_uno_user","string","false","\-","\-","Paramater user for dns_service 'dns_uno'" + "dns_vscale_key","string","false","\-","\-","Paramater key for dns_service 'dns_vscale'" + "dns_vultr_key","string","false","\-","\-","Paramater key for dns_service 'dns_vultr'" + "dns_yandex_token","string","false","\-","\-","Paramater token for dns_service 'dns_yandex'" + "dns_zilore_key","string","false","\-","\-","Paramater key for dns_service 'dns_zilore'" + "dns_zm_key","string","false","\-","\-","Paramater key for dns_service 'dns_zm'" + "dns_gdnsdk_user","string","false","\-","\-","Paramater user for dns_service 'dns_gdnsdk'" + "dns_gdnsdk_password","string","false","\-","\-","Paramater password for dns_service 'dns_gdnsdk'" + "dns_acmedns_user","string","false","\-","\-","Paramater user for dns_service 'dns_acmedns'" + "dns_acmedns_password","string","false","\-","\-","Paramater password for dns_service 'dns_acmedns'" + "dns_acmedns_subdomain","string","false","\-","\-","Paramater subdomain for dns_service 'dns_acmedns'" + "dns_acmedns_updateurl","string","false","\-","\-","Paramater updateurl for dns_service 'dns_acmedns'" + "dns_acmedns_baseurl","string","false","\-","\-","Paramater baseurl for dns_service 'dns_acmedns'" + "dns_acmeproxy_endpoint","string","false","\-","\-","Paramater endpoint for dns_service 'dns_acmeproxy'" + "dns_acmeproxy_username","string","false","\-","\-","Paramater username for dns_service 'dns_acmeproxy'" + "dns_acmeproxy_password","string","false","\-","\-","Paramater password for dns_service 'dns_acmeproxy'" + "dns_variomedia_key","string","false","\-","\-","Paramater key for dns_service 'dns_variomedia'" + "dns_schlundtech_user","string","false","\-","\-","Paramater user for dns_service 'dns_schlundtech'" + "dns_schlundtech_password","string","false","\-","\-","Paramater password for dns_service 'dns_schlundtech'" + "dns_easydns_apitoken","string","false","\-","\-","Paramater apitoken for dns_service 'dns_easydns'" + "dns_easydns_apikey","string","false","\-","\-","Paramater apikey for dns_service 'dns_easydns'" + "dns_euserv_user","string","false","\-","\-","Paramater user for dns_service 'dns_euserv'" + "dns_euserv_password","string","false","\-","\-","Paramater password for dns_service 'dns_euserv'" + "dns_leaseweb_key","string","false","\-","\-","Paramater key for dns_service 'dns_leaseweb'" + "dns_cn_user","string","false","\-","\-","Paramater user for dns_service 'dns_cn'" + "dns_cn_password","string","false","\-","\-","Paramater password for dns_service 'dns_cn'" + "dns_arvan_token","string","false","\-","\-","Paramater token for dns_service 'dns_arvan'" + "dns_artfiles_username","string","false","\-","\-","Paramater username for dns_service 'dns_artfiles'" + "dns_artfiles_password","string","false","\-","\-","Paramater password for dns_service 'dns_artfiles'" + "dns_hetzner_token","string","false","\-","\-","Paramater token for dns_service 'dns_hetzner'" + "dns_hexonet_login","string","false","\-","\-","Paramater login for dns_service 'dns_hexonet'" + "dns_hexonet_password","string","false","\-","\-","Paramater password for dns_service 'dns_hexonet'" + "dns_1984hosting_user","string","false","\-","\-","Paramater user for dns_service 'dns_1984hosting'" + "dns_1984hosting_password","string","false","\-","\-","Paramater password for dns_service 'dns_1984hosting'" + "dns_kas_login","string","false","\-","\-","Paramater login for dns_service 'dns_kas'" + "dns_kas_authdata","string","false","\-","\-","Paramater authdata for dns_service 'dns_kas'" + "dns_kas_authtype","string","false","plain","\-","Paramater authtype One of: 'plain', 'sha1' for dns_service 'dns_kas'" + "dns_desec_token","string","false","\-","\-","Paramater token for dns_service 'dns_desec'" + "dns_desec_name","string","false","\-","\-","Paramater name for dns_service 'dns_desec'" + "dns_infomaniak_token","string","false","\-","\-","Paramater token for dns_service 'dns_infomaniak'" + "dns_zone_username","string","false","\-","\-","Paramater username for dns_service 'dns_zone'" + "dns_zone_key","string","false","\-","\-","Paramater key for dns_service 'dns_zone'" + "dns_dynv6_token","string","false","\-","\-","Paramater token for dns_service 'dns_dynv6'" + "dns_cpanel_user","string","false","\-","\-","Paramater user for dns_service 'dns_cpanel'" + "dns_cpanel_token","string","false","\-","\-","Paramater token for dns_service 'dns_cpanel'" + "dns_cpanel_hostname","string","false","\-","\-","Paramater hostname for dns_service 'dns_cpanel'" + "dns_regru_username","string","false","\-","\-","Paramater username for dns_service 'dns_regru'" + "dns_regru_password","string","false","\-","\-","Paramater password for dns_service 'dns_regru'" + "dns_nic_username","string","false","\-","\-","Paramater username for dns_service 'dns_nic'" + "dns_nic_password","string","false","\-","\-","Paramater password for dns_service 'dns_nic'" + "dns_nic_client","string","false","\-","\-","Paramater client for dns_service 'dns_nic'" + "dns_nic_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_nic'" + "dns_world4you_username","string","false","\-","\-","Paramater username for dns_service 'dns_world4you'" + "dns_world4you_password","string","false","\-","\-","Paramater password for dns_service 'dns_world4you'" + "dns_aurora_key","string","false","\-","\-","Paramater key for dns_service 'dns_aurora'" + "dns_aurora_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_aurora'" + "dns_conoha_user","string","false","\-","\-","Paramater user for dns_service 'dns_conoha'" + "dns_conoha_password","string","false","\-","\-","Paramater password for dns_service 'dns_conoha'" + "dns_conoha_tenantid","string","false","\-","\-","Paramater tenantid for dns_service 'dns_conoha'" + "dns_conoha_idapi","string","false","\-","\-","Paramater idapi for dns_service 'dns_conoha'" + "dns_constellix_key","string","false","\-","\-","Paramater key for dns_service 'dns_constellix'" + "dns_constellix_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_constellix'" + "dns_exoscale_key","string","false","\-","\-","Paramater key for dns_service 'dns_exoscale'" + "dns_exoscale_secret","string","false","\-","\-","Paramater secret for dns_service 'dns_exoscale'" + "dns_internetbs_key","string","false","\-","\-","Paramater key for dns_service 'dns_internetbs'" + "dns_internetbs_password","string","false","\-","\-","Paramater password for dns_service 'dns_internetbs'" + "dns_pointhq_key","string","false","\-","\-","Paramater key for dns_service 'dns_pointhq'" + "dns_pointhq_email","string","false","\-","\-","Paramater email for dns_service 'dns_pointhq'" + "dns_rackspace_user","string","false","\-","\-","Paramater user for dns_service 'dns_rackspace'" + "dns_rackspace_key","string","false","\-","\-","Paramater key for dns_service 'dns_rackspace'" + "dns_rage4_token","string","false","\-","\-","Paramater token for dns_service 'dns_rage4'" + "dns_rage4_user","string","false","\-","\-","Paramater user for dns_service 'dns_rage4'" + +ansibleguy.opnsense.acme_action +=============================== + +.. csv-table:: Definition + :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" + :widths: 15 10 10 10 10 45 + + "name","string","true","\-","\-","Name to identify this automation." + "description","string","false","\-","desc","Description for this automation." + "type","string","false","\-","\-","Pre-defined commands for this automation. One of: '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","string","false","\-","\-","IP address or hostname of the SFTP server. For type: 'configd_upload_sftp'" + "sftp_host_key","string","false","\-","\-","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). . For type: 'configd_upload_sftp'" + "sftp_port","integer","false","22","\-","SFTP server port. For type: 'configd_upload_sftp'" + "sftp_user","string","false","\-","\-","The username to login to the SFTP server. For type: 'configd_upload_sftp'" + "sftp_identity_type","string","false","\-","\-","The type of identify to present to the SFTP server for authorization. One of: 'ecdsa', 'rsa', 'ed25519'. For type: 'configd_upload_sftp'" + "sftp_remote_path","string","false","\-","\-","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. For type: 'configd_upload_sftp'" + "sftp_chgrp","string","false","\-","\-","Unix group id to apply to all uploaded files. Leave blank to not change the group. For type: 'configd_upload_sftp'" + "sftp_chmod","string","false","\-","\-","Unix permission to apply to uploaded public keys. Leave blank to use default '0440'. For type: 'configd_upload_sftp'" + "sftp_chmod_key","string","false","\-","\-","Unix permission to apply to uploaded private keys. Leave blank to use default '0400'. For type: 'configd_upload_sftp'" + "sftp_filename_cert","string","false","\-","\-","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'. For type: 'configd_upload_sftp'" + "sftp_filename_key","string","false","\-","\-","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'. For type: 'configd_upload_sftp'" + "sftp_filename_ca","string","false","\-","\-","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'. For type: 'configd_upload_sftp'" + "sftp_filename_fullchain","string","false","\-","\-","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'. For type: 'configd_upload_sftp'" + "remote_ssh_host","string","false","\-","\-","IP address or hostname of the SSH server. For type: 'configd_remote_ssh'" + "remote_ssh_host_key","string","false","\-","\-","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). For type: 'configd_remote_ssh'" + "remote_ssh_port","integer","false","22","\-","SSH server port. Leave blank to use default '22'. For type: 'configd_remote_ssh'" + "remote_ssh_user","string","false","\-","\-","The username to login to the SSH server. For type: 'configd_remote_ssh'" + "remote_ssh_identity_type","string","false","\-","\-","The type of identify to present to the SSH server for authorization.' One of: 'ecdsa', 'rsa', 'ed25519'. For type: 'configd_remote_ssh'" + "remote_ssh_command","string","false","\-","\-","The command to execute on the SSH server. For type: 'configd_remote_ssh'" + "configd_generic_command","string","false","\-","\-","Select a pre-defined system command which should be run. For type: 'configd_generic'" + "acme_fritzbox_url","string","false","\-","\-","URL of the router, i.e. https://fritzbox.example.com. For type: 'acme_fritzbox'" + "acme_fritzbox_username","string","false","\-","\-","The username to login to the router. For type: 'acme_fritzbox' For type: 'acme_fritzbox'" + "acme_fritzbox_password","string","false","\-","\-","The password to login to the router. For type: 'acme_fritzbox'" + "acme_panos_username","string","false","\-","\-","The username to login to the firewall. For type: 'acme_panos'" + "acme_panos_password","string","false","\-","\-","The password to login to the firewall. For type: 'acme_panos'" + "acme_panos_host","string","false","\-","\-","The hostname of the router. For type: 'acme_panos'" + "acme_proxmoxve_user","string","false","root","\-","The user who owns the API key. Defaults to root. For type: 'acme_proxmoxve'" + "acme_proxmoxve_server","string","false","\-","\-","The hostname of the proxmox ve node. For type: 'acme_proxmoxve'" + "acme_proxmoxve_port","integer","false","8006","\-","The port number the management interface is on. Defaults to 8006. For type: 'acme_proxmoxve'" + "acme_proxmoxve_nodename","string","false","\-","\-","The name of the node we will be connecting to. For type: 'acme_proxmoxve'" + "acme_proxmoxve_realm","string","false","pam","\-","The authentication realm the user authenticates with. Defaults to pam. For type: 'acme_proxmoxve'" + "acme_proxmoxve_tokenid","string","false","acme","\-","The name of the API token created for the user account. Defaults to acme. For type: 'acme_proxmoxve'" + "acme_proxmoxve_tokenkey","string","false","\-","\-","The API token. For type: 'acme_proxmoxve'" + "acme_vault_url","string","false","\-","\-","URL of the Vault, i.e. http://vault.example.com:8200. For type: 'acme_vault'" + "acme_vault_prefix","string","false","acme","\-","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. For type: 'acme_vault'" + "acme_vault_token","string","false","\-","\-","This specifies the Vault token to authenticate with. For type: 'acme_vault'" + "acme_vault_kvv2","boolean","false","true\-","\-","If checked version 2 of the kv store will be used, otherwise version 1. For type: 'acme_vault'" + "acme_synology_dsm_hostname","string","false","\-","\-","Hostname of IP adress of the Synology DSM, i.e. synology.example.com or 192.168.0.1. For type: 'acme_synology'" + "acme_synology_dsm_port","integer","false","5000","\-","Port that will be used when connecting to Synology DSM. For type: 'acme_synology'" + "acme_synology_dsm_scheme","string","false","http","\-","Connection scheme that will be used when uploading certificates to Synology DSM. One of: 'http', 'https' For type: 'acme_synology'" + "acme_synology_dsm_username","string","false","\-","\-","Username to login, must be an administrator. For type: 'acme_synology'" + "acme_synology_dsm_password","string","false","\-","\-","Password to login with. For type: 'acme_synology'" + "acme_synology_dsm_create","boolean","false","true","\-","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. For type: 'acme_synology'" + "acme_synology_dsm_deviceid","string","false","\-","\-","If Synology DSM has OTP enabled, then the device ID has to be provided so that no OTP is required when running the automation. For type: 'acme_synology'" + "acme_synology_dsm_devicename","string","false","\-","\-","If Synology DSM has OTP enabled, then the device name has to be provided so that no OTP is required when running the automation. For type: 'acme_synology'" + "acme_truenas_apikey","string","false","\-","\-","API key generated in the TrueNAS web UI. For type: 'acme_truenas'" + "acme_truenas_hostname","string","false","\-","\-","Hostname or IP adress of TrueNAS Core Server. For type: 'acme_truenas'" + "acme_truenas_scheme","string","false","http","\-","Connection scheme that will be used when uploading certificates to TrueNAS Core Server. One of: 'http', 'https' For type: 'acme_truenas'" + "acme_unifi_keystore","string","false","/usr/local/share/java/unifi/data/keystore","\-","Path to the Unifi keystore file in the local filesystem, i.e. /usr/local/share/java/unifi/data/keystore. For type: 'acme_unifi'" + +ansibleguy.opnsense.acme_certificate +==================================== + +.. csv-table:: Definition + :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" + :widths: 15 10 10 10 10 45 + + "name","string","false","\-","\-","Common Name (CN) and first Alt Name (subjectAltName) for this certificate." + "description","string","true","\-","desc","Description for this certificate." + "alt_names","list","false","\-","\-","Configure additional names that should be part of the certificate, i.e. www.example.com or mail.example.com." + "account","string","false","\-","\-","Set the ACME CA account to use for this certificate." + "validation","string","false","\-","\-","Set the ACME challenge type for this certificate." + "auto_renew","boolean","false","true","\-","Enable automatic renewal for this certificate to prevent expiration. When disabled, the cron job will ignore this certificate." + "renew_interval","integer","false","60","\-","Specifies the days to renew the cert. The max value is 5000 days." + "key_length","string","false","key_4096","\-","Specify the domain key length: 2048, 3072, 4096, or ec-256, ec-384." + "ocsp","boolean","false","false","\-","Generate and add OCSP Must Staple extension to the certificate." + "restart_actions","list","false","\-","\-","Choose the automations that should be run after certificate creation and renewal." + "aliasmode","string","false","none","\-","Configure DNS alias mode to validate the certificate. One of: 'none', 'automatic', 'domain', 'challenge'" + "domainalias","string","false","\-","\-","When setting aliasmode to 'domain', enter the domain name that should be used for certificate validation. Please refer to the `acme.sh documentation `_ for further information." + "challengealias","string","false","\-","\-","When setting aliasmode to 'challenge', enter the domain name that should be used for certificate validation. Please refer to the `acme.sh documentation `_ for further information." + "reload","boolean","false","true","\-", .. include:: ../_include/param_reload.rst + +.. include:: ../_include/param_basic.rst + +Usage +***** + +Setting up this plugin for the first time involves the following steps + + * **Enable** the plugin with the acme_general module. + * Create an **account** with any of the supported CAs using the acme_account module. + * Set up a **validation** / challenge type using the acme_validation module. + * Add **actions** / automations using the acme_action module. This is optional, but recommended when using short-lived certificates. Automations allow to automatically run tasks when a certificate was created or renewed. + * Create **certificates**: Finally create the certificates using the acme_certificate module. + + +Examples +******** + +.. code-block:: yaml + + - hosts: localhost + gather_facts: false + module_defaults: + group/ansibleguy.opnsense.all: + firewall: 'opnsense.template.ansibleguy.net' + api_credential_file: '/home/guy/.secret/opn.key' + + tasks: + - name: Activate ACME Client + ansibleguy.opnsense.acme_general: + enable: true + # auto_renewal: true + # challenge_port: 43580 + # tls_challenge_port: 43581 + # restart_timeout: 600 + # haproxy_integration: false + # log_level: normal + # show_intro: true + # debug: false + + - name: Adding ACME Account + ansibleguy.opnsense.acme_account: + name: LE opnsense + # description: + # email: + # ca: letsencrypt + # eab_kid: + # eab_hmac: + # register: false + # enable: true + + - name: Adding ACME Validation + ansibleguy.opnsense.acme_validation: + name: HTTP + description: Default HTTP-01 Validation + method: http01 + # http_service: opnsense + # http_opn_autodiscovery: true + # http_opn_interface: wan + # http_opn_ipaddresses: ['1.2.3.4'] + + - name: Adding ACME Validation DNS + ansibleguy.opnsense.acme_validation: + name: DNS FreeDNS + description: DNS Validation w/ FreeDNS + # method: dns01 + # dns_service: dns_freedns + dns_freedns_user: USER + dns_freedns_password: SECRET + + - name: Adding ACME Action + ansibleguy.opnsense.acme_action: + name: Restart GUI + description: Restart OPNsense GUI + type: configd_restart_gui + + - name: Adding ACME Certificate + ansibleguy.opnsense.acme_action: + name: ansibleguy.net + description: LE ansibleguy.net + alt_names: ['ansibleguy.com'] + account: LE opnsense + validation: HTTP + # auto_renew: true + # renew_interval: 60 + # key_length: key_4096 + # ocsp: false + restart_actions: ['Restart GUI'] + # aliasmode: none + + - name: Listing jobs + ansibleguy.opnsense.list: + target: 'acme_certificate' + register: existing_certificates + + - name: Printing + ansible.builtin.debug: + var: existing_certificates.data diff --git a/meta/runtime.yml b/meta/runtime.yml index ee120d9..42762b2 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -127,6 +127,12 @@ action_groups: dhcp: - ansibleguy.opnsense.dhcp_reservation - ansibleguy.opnsense.dhcp_controlagent + acme: + - ansibleguy.opnsense.acme_general + - ansibleguy.opnsense.acme_account + - ansibleguy.opnsense.acme_validation + - ansibleguy.opnsense.acme_action + - ansibleguy.opnsense.acme_certificate all: - metadata: extend_group: @@ -150,6 +156,7 @@ action_groups: - ansibleguy.opnsense.openvpn - ansibleguy.opnsense.dhcrelay - ansibleguy.opnsense.dhcp + - ansibleguy.opnsense.acme plugin_routing: modules: @@ -197,3 +204,7 @@ plugin_routing: redirect: ansibleguy.opnsense.dhcrelay_destination unbound_domain: redirect: ansibleguy.opnsense.unbound_forward + acme_challenge: + redirect: ansibleguy.opnsense.acme_validation + acme_automation: + redirect: ansibleguy.opnsense.acme_action diff --git a/plugins/module_utils/main/acme_account.py b/plugins/module_utils/main/acme_account.py new file mode 100644 index 0000000..3368117 --- /dev/null +++ b/plugins/module_utils/main/acme_account.py @@ -0,0 +1,81 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ + validate_int_fields, is_unset +from ansible_collections.ansibleguy.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' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['description', 'custom_ca', 'eab_kid', 'eab_hmac'] + FIELDS_ALL = [ + 'enabled', 'name', 'email', 'ca', + ] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TYPING = { + 'bool': ['enabled'], + 'list': [], + 'select': ['ca'], + 'int': [], + } + EXIST_ATTR = 'account' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + self.account = {} + + def check(self) -> None: + self._base_check() + + 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 create(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.create() + + def update(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.update() + + def delete(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.delete() diff --git a/plugins/module_utils/main/acme_action.py b/plugins/module_utils/main/acme_action.py new file mode 100644 index 0000000..1970d7a --- /dev/null +++ b/plugins/module_utils/main/acme_action.py @@ -0,0 +1,103 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ + validate_int_fields, is_unset +from ansible_collections.ansibleguy.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' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + 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_TRANSLATE = { + #'field1': 'apifield1', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'acme_vault_kvv2', 'acme_synology_dsm_create'], + 'list': [], + 'select': ['type', 'remote_ssh_identity_type', 'acme_synology_dsm_scheme', 'acme_truenas_scheme'], + '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): + BaseModule.__init__(self=self, m=module, r=result, s=session) + 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!') + validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) + + 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 create(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.create() + + def update(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.update() + + def delete(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.delete() diff --git a/plugins/module_utils/main/acme_certificate.py b/plugins/module_utils/main/acme_certificate.py new file mode 100644 index 0000000..271cd79 --- /dev/null +++ b/plugins/module_utils/main/acme_certificate.py @@ -0,0 +1,131 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ + validate_int_fields, is_unset +from ansible_collections.ansibleguy.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' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + FIELDS_CHANGE = ['name', 'alt_names', 'account', 'validation', 'restart_actions', 'auto_renewal', 'renew_interval', 'aliasmode'] + FIELDS_ALL = [ + 'enabled', 'description', 'domainalias', 'challengealias' + ] + FIELDS_ALL.extend(FIELDS_CHANGE) + FIELDS_TRANSLATE = { + 'alt_names': 'altNames', + 'validation': 'validationMethod', + 'key_ength': 'keyLength', + 'restart_actions': 'restartActions', + 'auto_renewal': 'autoRenewal', + 'renew_interval': 'renewInterval', + } + FIELDS_TYPING = { + 'bool': ['enabled', 'auto_renewal'], + 'list': ['alt_names', 'restart_actions'], + 'select': ['account', 'validation', 'restart_actions', 'aliasmode'], + '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): + BaseModule.__init__(self=self, m=module, r=result, s=session) + 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!') + + validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) + + if self.p['aliasmode'] == 'domain': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['domainalias'] + elif self.p['aliasmode'] == 'challenge': + self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['challengealias'] + + self._base_check() + + if self.p['state'] == 'present': + if is_unset(self.p['account']): + self.m.fail_json(f"You need to provide an account to create/update certificates!") + else: + 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(f"You need to provide the validation to create/update certificates!") + else: + 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 create(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.create() + + def update(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.update() + + def delete(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.delete() diff --git a/plugins/module_utils/main/acme_general.py b/plugins/module_utils/main/acme_general.py new file mode 100644 index 0000000..98d85ee --- /dev/null +++ b/plugins/module_utils/main/acme_general.py @@ -0,0 +1,60 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.handler import \ + ModuleSoftError +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ + validate_int_fields, is_unset +from ansible_collections.ansibleguy.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'], + 'list': [], + '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) + self.settings = {} + + def check2(self) -> None: + if self.p['enabled']: + validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) + + self.settings = self._search_call() + self._build_diff() diff --git a/plugins/module_utils/main/acme_validation.py b/plugins/module_utils/main/acme_validation.py new file mode 100644 index 0000000..0712b72 --- /dev/null +++ b/plugins/module_utils/main/acme_validation.py @@ -0,0 +1,131 @@ +from ansible.module_utils.basic import AnsibleModule + +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ + Session +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ + validate_int_fields, is_unset +from ansible_collections.ansibleguy.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' + API_CONT_REL = 'service' + API_CMD_REL = 'reconfigure' + 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'], + 'list': ['http_opn_ipaddresses', 'http_haproxy_frontends', 'tlsalpn_acme_ipaddresses'], + 'select': ['method', 'http_service', 'http_opn_interface', 'tlsalpn_acme_interface', 'dns_service'], + 'int': [], + } + INT_VALIDATIONS = { + } + EXIST_ATTR = 'validation' + + def __init__(self, module: AnsibleModule, result: dict, session: Session = None): + BaseModule.__init__(self=self, m=module, r=result, s=session) + 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!') + validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_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 create(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.create() + + def update(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.update() + + def delete(self) -> None: + cont_get, mod_get = self.API_CONT, self.API_MOD + self.call_cnf['controller'] = cont_get + self.call_cnf['module'] = mod_get + self.b.delete() diff --git a/plugins/modules/acme_account.py b/plugins/modules/acme_account.py new file mode 100644 index 0000000..3c6e505 --- /dev/null +++ b/plugins/modules/acme_account.py @@ -0,0 +1,98 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2024, AnsibleGuy +# 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.ansibleguy.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.wrapper import module_wrapper + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_account import Account + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_account.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_account.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/plugins/modules/acme_action.py b/plugins/modules/acme_action.py new file mode 100644 index 0000000..362b445 --- /dev/null +++ b/plugins/modules/acme_action.py @@ -0,0 +1,281 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2024, AnsibleGuy +# 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.ansibleguy.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.wrapper import module_wrapper + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_action import Action + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_action.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_action.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/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py new file mode 100644 index 0000000..c6e6ef7 --- /dev/null +++ b/plugins/modules/acme_certificate.py @@ -0,0 +1,115 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2024, AnsibleGuy +# 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.ansibleguy.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.wrapper import module_wrapper + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_certificate import Certificate + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_account.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_account.html' + + +def run_module(): + module_args = dict( + name=dict( + type='str', required=False, + 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=[], + description='Optional e-mail address for this account.', + ), + 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/plugins/modules/acme_general.py b/plugins/modules/acme_general.py new file mode 100644 index 0000000..8e41697 --- /dev/null +++ b/plugins/modules/acme_general.py @@ -0,0 +1,93 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2024, AnsibleGuy +# 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.ansibleguy.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.wrapper import module_wrapper + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, EN_ONLY_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_general import General + +except MODULE_EXCEPTIONS: + module_dependency_error() + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_setting.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_setting.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) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/acme_validation.py b/plugins/modules/acme_validation.py new file mode 100644 index 0000000..94ad2ff --- /dev/null +++ b/plugins/modules/acme_validation.py @@ -0,0 +1,337 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (C) 2024, AnsibleGuy +# 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.ansibleguy.opnsense.plugins.module_utils.base.handler import \ + module_dependency_error, MODULE_EXCEPTIONS + +try: + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.wrapper import module_wrapper + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.defaults.main import \ + OPN_MOD_ARGS, STATE_MOD_ARG, RELOAD_MOD_ARG + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_validation import Validation + +except MODULE_EXCEPTIONS: + module_dependency_error() + + +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_validation.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_validation.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', + 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', + 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/plugins/modules/list.py b/plugins/modules/list.py index 696a043..5848d93 100644 --- a/plugins/modules/list.py +++ b/plugins/modules/list.py @@ -36,7 +36,8 @@ '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', + 'interface_lagg', 'interface_loopback', 'unbound_dnsbl', 'dhcp_reservation', 'acme_general', 'acme_account', + 'acme_validation', 'acme_action', 'acme_certificate', ] @@ -398,6 +399,25 @@ def run_module(): elif target == 'dhcp_reservation': from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.dhcp_reservation_v4 import \ ReservationV4 as Target_Obj + elif target == 'acme_general': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_general import \ + General as Target_Obj + + elif target == 'acme_account': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_account import \ + Account as Target_Objd + + elif target == 'acme_validation': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_validation import \ + Validation as Target_Obj + + elif target == 'acme_action': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_action import \ + Action as Target_Obj + + elif target == 'acme_certificate': + from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_certificate import \ + Certificate as Target_Obj except AttributeError: module_dependency_error() diff --git a/scripts/test.sh b/scripts/test.sh index 982719c..af998c3 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -155,6 +155,11 @@ run_test 'dhcp_controlagent' 1 run_test 'dhcp_reservation' 1 run_test 'system' 1 run_test 'package' 1 +run_test 'acme_general' 1 +run_test 'acme_account' 1 +run_test 'acme_validation' 1 +run_test 'acme_action' 1 +run_test 'acme_certificate' 0 echo '' echo '##############################' diff --git a/tests/acme_account.yml b/tests/acme_account.yml new file mode 100644 index 0000000..39aaa82 --- /dev/null +++ b/tests/acme_account.yml @@ -0,0 +1,132 @@ +--- + +- name: Testing ACME Account + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.list: + target: 'acme_account' + + tasks: + - name: Listing + ansibleguy.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + ansibleguy.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 + ansibleguy.opnsense.acme_account: + name: 'ANSIBLE_TEST_1_1' + ca: opnsense + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Adding 1 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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: Listing + ansibleguy.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 1 + when: not ansible_check_mode + + - name: Cleanup + ansibleguy.opnsense.acme_account: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + when: not ansible_check_mode + + - name: Listing + ansibleguy.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/tests/acme_action.yml b/tests/acme_action.yml new file mode 100644 index 0000000..03c72bb --- /dev/null +++ b/tests/acme_action.yml @@ -0,0 +1,398 @@ +--- + +- name: Testing ACME Action / Automations + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.list: + target: 'acme_action' + + tasks: + - name: Listing + ansibleguy.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + ansibleguy.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 + ansibleguy.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 + ansibleguy.opnsense.acme_action: + field1: '!INVALID-DOMAIN!' + field2: 'INVALID-IP' + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Adding 1 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 10 + when: not ansible_check_mode + + - name: Cleanup + ansibleguy.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 + ansibleguy.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/tests/acme_certificate.yml b/tests/acme_certificate.yml new file mode 100644 index 0000000..3735044 --- /dev/null +++ b/tests/acme_certificate.yml @@ -0,0 +1,331 @@ +--- + +- name: Testing ACME Certificate + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.list: + target: 'acme_certificate' + + tasks: + - name: Listing + ansibleguy.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + ansibleguy.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 + ansibleguy.opnsense.acme_account: + name: 'ANSIBLE_TEST_DUMMY_1_1' + ca: letsencrypt_test + + - name: Adding dummy validation + ansibleguy.opnsense.acme_validation: + name: 'ANSIBLE_TEST_DUMMY_1_1' + method: http01 + + - name: Adding dummy action + ansibleguy.opnsense.acme_action: + name: 'ANSIBLE_TEST_DUMMY_1_1' + type: configd_restart_gui + + - name: Adding 1 - failing because of missing name + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 3 + when: not ansible_check_mode + + - name: Cleanup + ansibleguy.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 + ansibleguy.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 + ansibleguy.opnsense.acme_account: + name: ANSIBLE_TEST_DUMMY_1_1 + state: 'absent' + diff: false + + - name: Cleanup Dummy Validation + ansibleguy.opnsense.acme_validation: + name: ANSIBLE_TEST_DUMMY_1_1 + state: 'absent' + diff: false + + - name: Cleanup Dummy Action + ansibleguy.opnsense.acme_action: + name: ANSIBLE_TEST_DUMMY_1_1 + state: 'absent' + diff: false diff --git a/tests/acme_general.yml b/tests/acme_general.yml new file mode 100644 index 0000000..5e3fa44 --- /dev/null +++ b/tests/acme_general.yml @@ -0,0 +1,84 @@ +--- + +- name: Testing ACME Setting + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.list: + target: 'acme_general' + + tasks: + - name: Listing + ansibleguy.opnsense.list: + register: opn_pre1 + failed_when: > + opn_pre1.failed or + 'data' not in opn_pre1 + + - name: Configuring - failing because of invalid challenge_port + ansibleguy.opnsense.acme_general: + challenge_port: 10 + register: opn_fail1 + failed_when: not opn_fail1.failed + + - name: Configuring - failing because of invalid tls_challenge_port + ansibleguy.opnsense.acme_general: + tls_challenge_port: 10 + register: opn_fail2 + failed_when: not opn_fail2.failed + + - name: Configuring - failing because of invalid restart_timeout + ansibleguy.opnsense.acme_general: + restart_timeout: 100000 + register: opn_fail3 + failed_when: not opn_fail3.failed + + - name: Configuring + ansibleguy.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 + ansibleguy.opnsense.acme_general: + enabled: false + auto_renewal: true + challenge_port: 43580 + tls_challenge_port: 43581 + restart_timeout: 600 + log_level: normal + show_intro: true + debug: true + register: opn2 + failed_when: > + opn2.failed or + not opn2.changed + when: not ansible_check_mode + + - name: Nothing changed + ansibleguy.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 diff --git a/tests/acme_validation.yml b/tests/acme_validation.yml new file mode 100644 index 0000000..b4bf30a --- /dev/null +++ b/tests/acme_validation.yml @@ -0,0 +1,358 @@ +--- + +- name: Testing ACME Validation + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.list: + target: 'acme_validation' + + tasks: + - name: Listing + ansibleguy.opnsense.list: + register: opn_pre1 + failed_when: > + 'data' not in opn_pre1 or + opn_pre1.data | length != 0 + + - name: Removing - does not exist + ansibleguy.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 + ansibleguy.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 + ansibleguy.opnsense.acme_validation: + name: 'ANSIBLE_TEST_1_1' + method: http01 + register: opn1 + failed_when: > + opn1.failed or + not opn1.changed + + - name: Changing 1 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.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 + ansibleguy.opnsense.list: + register: opn8 + failed_when: > + 'data' not in opn8 or + opn8.data | length != 10 + when: not ansible_check_mode + + - name: Cleanup + ansibleguy.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 + ansibleguy.opnsense.list: + register: opn_clean1 + failed_when: > + 'data' not in opn_clean1 or + opn_clean1.data | length != 0 + when: not ansible_check_mode From 515a4eac89e9d60033579621d43722c4f10c9857 Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Thu, 2 Jan 2025 20:21:59 +0100 Subject: [PATCH 2/9] Fix linter warnings --- plugins/module_utils/main/acme_account.py | 2 - plugins/module_utils/main/acme_certificate.py | 79 ++++++++++--------- plugins/module_utils/main/acme_general.py | 4 +- plugins/module_utils/main/acme_validation.py | 79 +++++++++++-------- plugins/modules/acme_action.py | 40 +++++++--- plugins/modules/acme_validation.py | 34 +++++++- 6 files changed, 148 insertions(+), 90 deletions(-) diff --git a/plugins/module_utils/main/acme_account.py b/plugins/module_utils/main/acme_account.py index 3368117..a850cb9 100644 --- a/plugins/module_utils/main/acme_account.py +++ b/plugins/module_utils/main/acme_account.py @@ -2,8 +2,6 @@ from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ Session -from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ - validate_int_fields, is_unset from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule diff --git a/plugins/module_utils/main/acme_certificate.py b/plugins/module_utils/main/acme_certificate.py index 271cd79..7ddf2aa 100644 --- a/plugins/module_utils/main/acme_certificate.py +++ b/plugins/module_utils/main/acme_certificate.py @@ -22,7 +22,9 @@ class Certificate(BaseModule): API_CONT_GET = 'settings' API_CONT_REL = 'service' API_CMD_REL = 'reconfigure' - FIELDS_CHANGE = ['name', 'alt_names', 'account', 'validation', 'restart_actions', 'auto_renewal', 'renew_interval', 'aliasmode'] + FIELDS_CHANGE = [ + 'name', 'alt_names', 'account', 'validation', 'restart_actions', 'auto_renewal', 'renew_interval', 'aliasmode' + ] FIELDS_ALL = [ 'enabled', 'description', 'domainalias', 'challengealias' ] @@ -73,44 +75,47 @@ def check(self) -> None: self._base_check() if self.p['state'] == 'present': - if is_unset(self.p['account']): - self.m.fail_json(f"You need to provide an account to create/update certificates!") + 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: + for key, values in self.existing_accounts.items(): + if values['name'] == self.p['account']: + self.p['account'] = key + break else: - 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(f"You need to provide the validation to create/update certificates!") + 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: + for key, values in self.existing_validations.items(): + if values['name'] == self.p['validation']: + self.p['validation'] = key + break else: - 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'] - ] + 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 create(self) -> None: cont_get, mod_get = self.API_CONT, self.API_MOD diff --git a/plugins/module_utils/main/acme_general.py b/plugins/module_utils/main/acme_general.py index 98d85ee..3f192b9 100644 --- a/plugins/module_utils/main/acme_general.py +++ b/plugins/module_utils/main/acme_general.py @@ -1,11 +1,9 @@ from ansible.module_utils.basic import AnsibleModule -from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.handler import \ - ModuleSoftError from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ Session from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ - validate_int_fields, is_unset + validate_int_fields from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import GeneralModule diff --git a/plugins/module_utils/main/acme_validation.py b/plugins/module_utils/main/acme_validation.py index 0712b72..8583b38 100644 --- a/plugins/module_utils/main/acme_validation.py +++ b/plugins/module_utils/main/acme_validation.py @@ -25,39 +25,54 @@ class Validation(BaseModule): 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', + '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', + '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 = { diff --git a/plugins/modules/acme_action.py b/plugins/modules/acme_action.py index 362b445..fcf8941 100644 --- a/plugins/modules/acme_action.py +++ b/plugins/modules/acme_action.py @@ -48,7 +48,8 @@ def run_module(): ), 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).' + 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, @@ -65,7 +66,8 @@ def run_module(): ), 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.' + 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, @@ -81,19 +83,26 @@ def run_module(): ), 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".' + 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".' + 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".' + 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".' + 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( @@ -102,7 +111,8 @@ def run_module(): ), 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).' + 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, @@ -188,7 +198,9 @@ def run_module(): ), 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.' + 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, @@ -222,15 +234,18 @@ def run_module(): ), 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.' + 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.' + 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.' + 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( @@ -249,7 +264,8 @@ def run_module(): # 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.' + 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, diff --git a/plugins/modules/acme_validation.py b/plugins/modules/acme_validation.py index 94ad2ff..55fb5c2 100644 --- a/plugins/modules/acme_validation.py +++ b/plugins/modules/acme_validation.py @@ -57,11 +57,15 @@ def run_module(): ), 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.', + 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. ', + 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, @@ -77,11 +81,33 @@ def run_module(): ), 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'], + 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.', + 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), From aaef01d4c26166f97c1a1a1582d7580acd40ded3 Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Thu, 2 Jan 2025 20:23:59 +0100 Subject: [PATCH 3/9] Wrap search call to reset controller / module --- plugins/module_utils/main/acme_account.py | 16 ++++------------ plugins/module_utils/main/acme_action.py | 16 ++++------------ plugins/module_utils/main/acme_certificate.py | 16 ++++------------ plugins/module_utils/main/acme_validation.py | 16 ++++------------ 4 files changed, 16 insertions(+), 48 deletions(-) diff --git a/plugins/module_utils/main/acme_account.py b/plugins/module_utils/main/acme_account.py index a850cb9..c5196c3 100644 --- a/plugins/module_utils/main/acme_account.py +++ b/plugins/module_utils/main/acme_account.py @@ -60,20 +60,12 @@ def register(self) -> None: 'command': 'register', }) - def create(self) -> None: - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - self.b.create() + def _search_call(self) -> list: + result = self.b.search() - def update(self) -> None: + # Reset controller and module cont_get, mod_get = self.API_CONT, self.API_MOD self.call_cnf['controller'] = cont_get self.call_cnf['module'] = mod_get - self.b.update() - def delete(self) -> None: - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - self.b.delete() + return result diff --git a/plugins/module_utils/main/acme_action.py b/plugins/module_utils/main/acme_action.py index 1970d7a..cef9924 100644 --- a/plugins/module_utils/main/acme_action.py +++ b/plugins/module_utils/main/acme_action.py @@ -84,20 +84,12 @@ def check(self) -> None: self._base_check() - def create(self) -> None: - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - self.b.create() + def _search_call(self) -> list: + result = self.b.search() - def update(self) -> None: + # Reset controller and module cont_get, mod_get = self.API_CONT, self.API_MOD self.call_cnf['controller'] = cont_get self.call_cnf['module'] = mod_get - self.b.update() - def delete(self) -> None: - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - self.b.delete() + return result diff --git a/plugins/module_utils/main/acme_certificate.py b/plugins/module_utils/main/acme_certificate.py index 7ddf2aa..a593710 100644 --- a/plugins/module_utils/main/acme_certificate.py +++ b/plugins/module_utils/main/acme_certificate.py @@ -117,20 +117,12 @@ def resolve_relations(self) -> None: for action in self.p['restart_actions'] ] - def create(self) -> None: - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - self.b.create() + def _search_call(self) -> list: + result = self.b.search() - def update(self) -> None: + # Reset controller and module cont_get, mod_get = self.API_CONT, self.API_MOD self.call_cnf['controller'] = cont_get self.call_cnf['module'] = mod_get - self.b.update() - def delete(self) -> None: - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - self.b.delete() + return result diff --git a/plugins/module_utils/main/acme_validation.py b/plugins/module_utils/main/acme_validation.py index 8583b38..338ff69 100644 --- a/plugins/module_utils/main/acme_validation.py +++ b/plugins/module_utils/main/acme_validation.py @@ -127,20 +127,12 @@ def check(self) -> None: ] self._base_check() - def create(self) -> None: - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - self.b.create() + def _search_call(self) -> list: + result = self.b.search() - def update(self) -> None: + # Reset controller and module cont_get, mod_get = self.API_CONT, self.API_MOD self.call_cnf['controller'] = cont_get self.call_cnf['module'] = mod_get - self.b.update() - def delete(self) -> None: - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - self.b.delete() + return result From ba8fe9efd5ce2febe50ebb9f2ecf13bb85a54883 Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Thu, 2 Jan 2025 20:40:02 +0100 Subject: [PATCH 4/9] No reload required --- plugins/module_utils/main/acme_account.py | 4 ++++ plugins/module_utils/main/acme_action.py | 4 ++++ plugins/module_utils/main/acme_certificate.py | 4 ++++ plugins/module_utils/main/acme_validation.py | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/plugins/module_utils/main/acme_account.py b/plugins/module_utils/main/acme_account.py index c5196c3..70dbcbe 100644 --- a/plugins/module_utils/main/acme_account.py +++ b/plugins/module_utils/main/acme_account.py @@ -60,6 +60,10 @@ def register(self) -> None: 'command': 'register', }) + def reload(self) -> dict: + # no reload required + pass + def _search_call(self) -> list: result = self.b.search() diff --git a/plugins/module_utils/main/acme_action.py b/plugins/module_utils/main/acme_action.py index cef9924..5600cd5 100644 --- a/plugins/module_utils/main/acme_action.py +++ b/plugins/module_utils/main/acme_action.py @@ -84,6 +84,10 @@ def check(self) -> None: self._base_check() + def reload(self) -> dict: + # no reload required + pass + def _search_call(self) -> list: result = self.b.search() diff --git a/plugins/module_utils/main/acme_certificate.py b/plugins/module_utils/main/acme_certificate.py index a593710..2b71d35 100644 --- a/plugins/module_utils/main/acme_certificate.py +++ b/plugins/module_utils/main/acme_certificate.py @@ -117,6 +117,10 @@ def resolve_relations(self) -> None: for action in self.p['restart_actions'] ] + def reload(self) -> dict: + # no reload required + pass + def _search_call(self) -> list: result = self.b.search() diff --git a/plugins/module_utils/main/acme_validation.py b/plugins/module_utils/main/acme_validation.py index 338ff69..f2021c6 100644 --- a/plugins/module_utils/main/acme_validation.py +++ b/plugins/module_utils/main/acme_validation.py @@ -127,6 +127,10 @@ def check(self) -> None: ] self._base_check() + def reload(self) -> dict: + # no reload required + pass + def _search_call(self) -> list: result = self.b.search() From 28676b96e3878f879652ad064996107b5a41d3f3 Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Thu, 2 Jan 2025 20:40:47 +0100 Subject: [PATCH 5/9] Improve linelength --- docs/source/modules/acmeclient.rst | 4 ++-- plugins/modules/acme_account.py | 14 +++++--------- plugins/modules/acme_action.py | 7 +++---- plugins/modules/acme_certificate.py | 25 +++++++++++-------------- plugins/modules/acme_general.py | 23 ++++++++++------------- 5 files changed, 31 insertions(+), 42 deletions(-) diff --git a/docs/source/modules/acmeclient.rst b/docs/source/modules/acmeclient.rst index 61c15c3..93c1ebf 100644 --- a/docs/source/modules/acmeclient.rst +++ b/docs/source/modules/acmeclient.rst @@ -383,9 +383,9 @@ ansibleguy.opnsense.acme_certificate :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" :widths: 15 10 10 10 10 45 - "name","string","false","\-","\-","Common Name (CN) and first Alt Name (subjectAltName) for this certificate." + "name","string","false","\-","cn","Common Name (CN) and first Alt Name (subjectAltName) for this certificate." "description","string","true","\-","desc","Description for this certificate." - "alt_names","list","false","\-","\-","Configure additional names that should be part of the certificate, i.e. www.example.com or mail.example.com." + "alt_names","list","false","\-","subject_alt_name","Configure additional names that should be part of the certificate, i.e. www.example.com or mail.example.com." "account","string","false","\-","\-","Set the ACME CA account to use for this certificate." "validation","string","false","\-","\-","Set the ACME challenge type for this certificate." "auto_renew","boolean","false","true","\-","Enable automatic renewal for this certificate to prevent expiration. When disabled, the cron job will ignore this certificate." diff --git a/plugins/modules/acme_account.py b/plugins/modules/acme_account.py index 3c6e505..6219d21 100644 --- a/plugins/modules/acme_account.py +++ b/plugins/modules/acme_account.py @@ -40,26 +40,22 @@ def run_module(): ca=dict( type='str', required=False, default='letsencrypt', choices=[ - 'buypass', 'buypass_test', 'google', 'google_test', - 'letsencrypt', 'letsencrypt_test', 'sslcom', + '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' + 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).', + 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).', + description='An value provided by the CA when using ACME External Account Binding (EAB).', ), register=dict( type='bool', required=False, default=False, diff --git a/plugins/modules/acme_action.py b/plugins/modules/acme_action.py index fcf8941..bfc777a 100644 --- a/plugins/modules/acme_action.py +++ b/plugins/modules/acme_action.py @@ -36,10 +36,9 @@ def run_module(): 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', + '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( diff --git a/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py index c6e6ef7..948c1c7 100644 --- a/plugins/modules/acme_certificate.py +++ b/plugins/modules/acme_certificate.py @@ -26,7 +26,7 @@ def run_module(): module_args = dict( name=dict( - type='str', required=False, + type='str', required=False, aliases=['cn'], description='Common Name (CN) and first Alt Name (subjectAltName) for this certificate.', ), description=dict( @@ -34,16 +34,16 @@ def run_module(): description='Description for this certificate.', ), alt_names=dict( - type='list', required=False, elements='str', default=[], - description='Optional e-mail address for this account.', + 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.', + 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, @@ -60,8 +60,7 @@ def run_module(): ), restart_actions=dict( type='list', required=False, elements='str', default=[], - description='Choose the automations that should be run after certificate ' - 'creation and renewal.', + description='Choose the automations that should be run after certificate creation and renewal.', ), aliasmode=dict( type='str', required=False, default='none', @@ -70,15 +69,13 @@ def run_module(): ), 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. ', + 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 ' + 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, diff --git a/plugins/modules/acme_general.py b/plugins/modules/acme_general.py index 8e41697..cdbc377 100644 --- a/plugins/modules/acme_general.py +++ b/plugins/modules/acme_general.py @@ -26,28 +26,25 @@ 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.', + 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.', + 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.', + 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.', + 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, From 3118ee2e7ed1165fec632e41eace9ec16532cca5 Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Fri, 3 Jan 2025 18:10:20 +0100 Subject: [PATCH 6/9] Fix docu links --- plugins/modules/acme_account.py | 4 ++-- plugins/modules/acme_action.py | 4 ++-- plugins/modules/acme_certificate.py | 4 ++-- plugins/modules/acme_general.py | 4 ++-- plugins/modules/acme_validation.py | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/modules/acme_account.py b/plugins/modules/acme_account.py index 6219d21..1e67576 100644 --- a/plugins/modules/acme_account.py +++ b/plugins/modules/acme_account.py @@ -19,8 +19,8 @@ module_dependency_error() -# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_account.html' -# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_account.html' +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' def run_module(): diff --git a/plugins/modules/acme_action.py b/plugins/modules/acme_action.py index bfc777a..f8fa1bc 100644 --- a/plugins/modules/acme_action.py +++ b/plugins/modules/acme_action.py @@ -19,8 +19,8 @@ module_dependency_error() -# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_action.html' -# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_action.html' +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' def run_module(): diff --git a/plugins/modules/acme_certificate.py b/plugins/modules/acme_certificate.py index 948c1c7..25a539a 100644 --- a/plugins/modules/acme_certificate.py +++ b/plugins/modules/acme_certificate.py @@ -19,8 +19,8 @@ module_dependency_error() -# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_account.html' -# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_account.html' +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' def run_module(): diff --git a/plugins/modules/acme_general.py b/plugins/modules/acme_general.py index cdbc377..84c1ba8 100644 --- a/plugins/modules/acme_general.py +++ b/plugins/modules/acme_general.py @@ -18,8 +18,8 @@ except MODULE_EXCEPTIONS: module_dependency_error() -# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_setting.html' -# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_setting.html' +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' def run_module(): diff --git a/plugins/modules/acme_validation.py b/plugins/modules/acme_validation.py index 55fb5c2..440df40 100644 --- a/plugins/modules/acme_validation.py +++ b/plugins/modules/acme_validation.py @@ -19,8 +19,8 @@ module_dependency_error() -# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_validation.html' -# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acme_validation.html' +# DOCUMENTATION = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' +# EXAMPLES = 'https://opnsense.ansibleguy.net/en/latest/modules/acmeclient.html' def run_module(): From d64efc31b1d138ca9d564ec01ccc6abff3b2075e Mon Sep 17 00:00:00 2001 From: Marius Rieder Date: Fri, 3 Jan 2025 18:25:10 +0100 Subject: [PATCH 7/9] Fix typo --- plugins/modules/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/modules/list.py b/plugins/modules/list.py index 5848d93..1aa02c5 100644 --- a/plugins/modules/list.py +++ b/plugins/modules/list.py @@ -405,7 +405,7 @@ def run_module(): elif target == 'acme_account': from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_account import \ - Account as Target_Objd + Account as Target_Obj elif target == 'acme_validation': from ansible_collections.ansibleguy.opnsense.plugins.module_utils.main.acme_validation import \ From a9889eb268eb9e44a289b3181d83e6874e1e48d4 Mon Sep 17 00:00:00 2001 From: Rath Pascal Date: Sun, 5 Jan 2025 16:02:46 +0100 Subject: [PATCH 8/9] fixes for ACME-client modules and tests --- README.md | 9 ++- docs/source/modules/acmeclient.rst | 18 ++++-- docs/source/modules/dhcp.rst | 2 +- docs/source/usage/4_develop.rst | 2 +- plugins/module_utils/base/base.py | 3 + plugins/module_utils/base/cls.py | 3 + plugins/module_utils/main/acme_account.py | 19 +----- plugins/module_utils/main/acme_action.py | 24 ++------ plugins/module_utils/main/acme_certificate.py | 45 +++++++------- plugins/module_utils/main/acme_general.py | 8 --- plugins/module_utils/main/acme_validation.py | 32 +++++----- plugins/modules/acme_validation.py | 4 +- scripts/test.sh | 5 +- scripts/test_cleanup.sh | 2 +- tests/{cleanup.yml => 1_cleanup.yml} | 58 ++++++++++++++++++- tests/1_dependencies.yml | 24 ++++++++ tests/README.md | 3 + tests/acme_certificate.yml | 6 +- tests/acme_general.yml | 5 +- 19 files changed, 165 insertions(+), 107 deletions(-) rename tests/{cleanup.yml => 1_cleanup.yml} (92%) create mode 100644 tests/1_dependencies.yml diff --git a/README.md b/README.md index dd57a38..3382de9 100644 --- a/README.md +++ b/README.md @@ -93,8 +93,8 @@ not implemented => development => [testing](https://github.com/ansibleguy/collec ### Implemented -| Function | Module | Usage | State | -|:--------------------------|:-----------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|:---------| +| Function | Module | Usage | State | +|:--------------------------|:-----------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------|:---------| | **Base** | ansibleguy.opnsense.list | [Docs](https://opnsense.ansibleguy.net/modules/2_list.html) | stable | | **Base** | ansibleguy.opnsense.reload | [Docs](https://opnsense.ansibleguy.net/modules/2_reload.html) | stable | | **Services** | ansibleguy.opnsense.service | [Docs](https://opnsense.ansibleguy.net/modules/service.html) | stable | @@ -195,6 +195,11 @@ not implemented => development => [testing](https://github.com/ansibleguy/collec | **DHCP Relay** | ansibleguy.opnsense.dhcrelay_destination | [Docs](https://opnsense.ansibleguy.net/modules/dhcrelay_destination.html) | unstable | | **DHCP Reservation** | ansibleguy.opnsense.dhcp_reservation | [Docs](https://opnsense.ansibleguy.net/modules/dhcp.html) | unstable | | **DHCP Controlagent** | ansibleguy.opnsense.dhcp_controlagent | [Docs](https://opnsense.ansibleguy.net/modules/dhcp.html) | unstable | +| **ACME (Certificates)** | ansibleguy.opnsense.acme_account | [Docs](https://opnsense.ansibleguy.net/modules/acmeclient.html) | unstable | +| **ACME (Certificates)** | ansibleguy.opnsense.acme_action | [Docs](https://opnsense.ansibleguy.net/modules/acmeclient.html) | unstable | +| **ACME (Certificates)** | ansibleguy.opnsense.acme_general | [Docs](https://opnsense.ansibleguy.net/modules/acmeclient.html) | unstable | +| **ACME (Certificates)** | ansibleguy.opnsense.acme_validation | [Docs](https://opnsense.ansibleguy.net/modules/acmeclient.html) | unstable | +| **ACME (Certificates)** | ansibleguy.opnsense.acme_certificate | [Docs](https://opnsense.ansibleguy.net/modules/acmeclient.html) | unstable | ### Roadmap diff --git a/docs/source/modules/acmeclient.rst b/docs/source/modules/acmeclient.rst index 93c1ebf..b277e6e 100644 --- a/docs/source/modules/acmeclient.rst +++ b/docs/source/modules/acmeclient.rst @@ -8,7 +8,12 @@ ACME Client **STATE**: unstable -**TESTS**: `acme_certificate `_ +**TESTS**: `acme_account `_ | +`acme_action `_ | +`acme_certificate `_ | +`acme_general `_ | +`acme_validation `_ + **API Docs**: `Plugins - Acmeclient `_ @@ -16,7 +21,7 @@ ACME Client Contribution ************ -Thanks to `@jiuka `_ for developing this module! +Thanks to `@jiuka `_ for developing these modules! Prerequisites ************* @@ -27,7 +32,9 @@ You need to install the FRR plugin: os-acme-client ``` -You can also install it using the package module. +You can also install it using the `package module `_. + +---- Definition ********** @@ -35,7 +42,7 @@ Definition .. include:: ../_include/param_basic.rst ansibleguy.opnsense.acme_general -==================================== +================================ .. csv-table:: Definition :header: "Parameter", "Type", "Required", "Default", "Aliases", "Comment" @@ -400,6 +407,8 @@ ansibleguy.opnsense.acme_certificate .. include:: ../_include/param_basic.rst +---- + Usage ***** @@ -411,6 +420,7 @@ Setting up this plugin for the first time involves the following steps * Add **actions** / automations using the acme_action module. This is optional, but recommended when using short-lived certificates. Automations allow to automatically run tasks when a certificate was created or renewed. * Create **certificates**: Finally create the certificates using the acme_certificate module. +---- Examples ******** diff --git a/docs/source/modules/dhcp.rst b/docs/source/modules/dhcp.rst index 166f60b..afec40a 100644 --- a/docs/source/modules/dhcp.rst +++ b/docs/source/modules/dhcp.rst @@ -18,7 +18,7 @@ DHCP Contribution ************ -Thanks to `@KalleDK `_ for developing these module! +Thanks to `@KalleDK `_ for developing these modules! ---- diff --git a/docs/source/usage/4_develop.rst b/docs/source/usage/4_develop.rst index 3bbb0df..ee2440b 100644 --- a/docs/source/usage/4_develop.rst +++ b/docs/source/usage/4_develop.rst @@ -389,7 +389,7 @@ There are `module-templates /tests/cleanup.yml` (set state we will expect when re-running the tests) + - Add a cleanup-task in :code:`/tests/1_cleanup.yml` (set state we will expect when re-running the tests) - Enable the test once it runs successfully - add it to :code:`/scripts/test.sh` diff --git a/plugins/module_utils/base/base.py b/plugins/module_utils/base/base.py index b4eda4d..8ffc594 100644 --- a/plugins/module_utils/base/base.py +++ b/plugins/module_utils/base/base.py @@ -197,6 +197,9 @@ def find(self, match_fields: list) -> None: 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'): diff --git a/plugins/module_utils/base/cls.py b/plugins/module_utils/base/cls.py index 2b09f2e..8376754 100644 --- a/plugins/module_utils/base/cls.py +++ b/plugins/module_utils/base/cls.py @@ -48,6 +48,9 @@ def _base_check(self, match_fields: list = None): if 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() diff --git a/plugins/module_utils/main/acme_account.py b/plugins/module_utils/main/acme_account.py index 70dbcbe..4beda41 100644 --- a/plugins/module_utils/main/acme_account.py +++ b/plugins/module_utils/main/acme_account.py @@ -18,8 +18,6 @@ class Account(BaseModule): API_MOD = 'acmeclient' API_CONT = 'accounts' API_CONT_GET = 'settings' - API_CONT_REL = 'service' - API_CMD_REL = 'reconfigure' FIELDS_CHANGE = ['description', 'custom_ca', 'eab_kid', 'eab_hmac'] FIELDS_ALL = [ 'enabled', 'name', 'email', 'ca', @@ -27,9 +25,7 @@ class Account(BaseModule): FIELDS_ALL.extend(FIELDS_CHANGE) FIELDS_TYPING = { 'bool': ['enabled'], - 'list': [], 'select': ['ca'], - 'int': [], } EXIST_ATTR = 'account' @@ -37,9 +33,6 @@ def __init__(self, module: AnsibleModule, result: dict, session: Session = None) BaseModule.__init__(self=self, m=module, r=result, s=session) self.account = {} - def check(self) -> None: - self._base_check() - def process(self) -> None: self.b.process() @@ -60,16 +53,6 @@ def register(self) -> None: 'command': 'register', }) - def reload(self) -> dict: + def reload(self): # no reload required pass - - def _search_call(self) -> list: - result = self.b.search() - - # Reset controller and module - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - - return result diff --git a/plugins/module_utils/main/acme_action.py b/plugins/module_utils/main/acme_action.py index 5600cd5..6c45024 100644 --- a/plugins/module_utils/main/acme_action.py +++ b/plugins/module_utils/main/acme_action.py @@ -20,8 +20,6 @@ class Action(BaseModule): API_MOD = 'acmeclient' API_CONT = 'actions' API_CONT_GET = 'settings' - API_CONT_REL = 'service' - API_CMD_REL = 'reconfigure' FIELDS_CHANGE = ['type'] FIELDS_ALL = [ 'enabled', 'name', 'description', @@ -53,13 +51,12 @@ class Action(BaseModule): 'acme_unifi_keystore', ] FIELDS_ALL.extend(FIELDS_CHANGE) - FIELDS_TRANSLATE = { - #'field1': 'apifield1', - } FIELDS_TYPING = { 'bool': ['enabled', 'acme_vault_kvv2', 'acme_synology_dsm_create'], - 'list': [], - 'select': ['type', 'remote_ssh_identity_type', 'acme_synology_dsm_scheme', 'acme_truenas_scheme'], + '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 = { @@ -75,6 +72,7 @@ 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!') + validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) if self.p['type'].startswith('acme_'): @@ -84,16 +82,6 @@ def check(self) -> None: self._base_check() - def reload(self) -> dict: + def reload(self): # no reload required pass - - def _search_call(self) -> list: - result = self.b.search() - - # Reset controller and module - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - - return result diff --git a/plugins/module_utils/main/acme_certificate.py b/plugins/module_utils/main/acme_certificate.py index 2b71d35..e4114a7 100644 --- a/plugins/module_utils/main/acme_certificate.py +++ b/plugins/module_utils/main/acme_certificate.py @@ -20,8 +20,6 @@ class Certificate(BaseModule): API_MOD = 'acmeclient' API_CONT = 'certificates' API_CONT_GET = 'settings' - API_CONT_REL = 'service' - API_CMD_REL = 'reconfigure' FIELDS_CHANGE = [ 'name', 'alt_names', 'account', 'validation', 'restart_actions', 'auto_renewal', 'renew_interval', 'aliasmode' ] @@ -68,33 +66,40 @@ def check(self) -> None: validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) if self.p['aliasmode'] == 'domain': - self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['domainalias'] + self.FIELDS_CHANGE.append('domainalias') + elif self.p['aliasmode'] == 'challenge': - self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['challengealias'] + self.FIELDS_CHANGE.append('challengealias') self._base_check() if self.p['state'] == 'present': - self.resolve_relations() + self._resolve_relations() - def resolve_relations(self) -> None: + 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: - for key, values in self.existing_accounts.items(): - if values['name'] == self.p['account']: - self.p['account'] = key - break + 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: - for key, values in self.existing_validations.items(): - if values['name'] == self.p['validation']: - self.p['validation'] = key - break + 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!") @@ -117,16 +122,6 @@ def resolve_relations(self) -> None: for action in self.p['restart_actions'] ] - def reload(self) -> dict: + def reload(self): # no reload required pass - - def _search_call(self) -> list: - result = self.b.search() - - # Reset controller and module - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - - return result diff --git a/plugins/module_utils/main/acme_general.py b/plugins/module_utils/main/acme_general.py index 3f192b9..09ada5f 100644 --- a/plugins/module_utils/main/acme_general.py +++ b/plugins/module_utils/main/acme_general.py @@ -35,7 +35,6 @@ class General(GeneralModule): } FIELDS_TYPING = { 'bool': ['enabled', 'auto_renewal', 'haproxy_integration', 'show_intro'], - 'list': [], 'select': ['log_level'], 'int': ['challenge_port', 'tls_challenge_port', 'restart_timeout'], } @@ -49,10 +48,3 @@ class General(GeneralModule): def __init__(self, module: AnsibleModule, result: dict, session: Session = None): GeneralModule.__init__(self=self, m=module, r=result, s=session) self.settings = {} - - def check2(self) -> None: - if self.p['enabled']: - validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) - - self.settings = self._search_call() - self._build_diff() diff --git a/plugins/module_utils/main/acme_validation.py b/plugins/module_utils/main/acme_validation.py index f2021c6..c847ab3 100644 --- a/plugins/module_utils/main/acme_validation.py +++ b/plugins/module_utils/main/acme_validation.py @@ -20,8 +20,6 @@ class Validation(BaseModule): API_MOD = 'acmeclient' API_CONT = 'validations' API_CONT_GET = 'settings' - API_CONT_REL = 'service' - API_CMD_REL = 'reconfigure' FIELDS_CHANGE = ['description', 'method'] FIELDS_ALL = [ 'name', @@ -80,12 +78,15 @@ class Validation(BaseModule): 'http_haproxy_frontends': 'http_haproxyFrontends', } FIELDS_TYPING = { - 'bool': ['enabled', 'http_opn_autodiscovery', 'http_haproxy_inject', 'tlsalpn_acme_autodiscovery'], + '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'], - 'int': [], - } - INT_VALIDATIONS = { + 'select': [ + 'method', 'http_service', 'http_opn_interface', 'tlsalpn_acme_interface', 'dns_service', + 'dns_kas_authtype', + ], } EXIST_ATTR = 'validation' @@ -97,7 +98,6 @@ 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!') - validate_int_fields(module=self.m, data=self.p, field_minmax=self.INT_VALIDATIONS) if self.p['method'] == 'http01': self.FIELDS_CHANGE = self.FIELDS_CHANGE + ['http_service'] @@ -107,36 +107,30 @@ def check(self) -> None: 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) -> dict: + def reload(self): # no reload required pass - - def _search_call(self) -> list: - result = self.b.search() - - # Reset controller and module - cont_get, mod_get = self.API_CONT, self.API_MOD - self.call_cnf['controller'] = cont_get - self.call_cnf['module'] = mod_get - - return result diff --git a/plugins/modules/acme_validation.py b/plugins/modules/acme_validation.py index 440df40..3d11936 100644 --- a/plugins/modules/acme_validation.py +++ b/plugins/modules/acme_validation.py @@ -52,7 +52,7 @@ def run_module(): description='Choose the interface where this IP address is currently configured.', ), http_opn_ipaddresses=dict( - type='list', required=False, elements='str', + type='list', required=False, elements='str', default=[], description='Enter the all of these IP addresses here.', ), http_haproxy_inject=dict( @@ -76,7 +76,7 @@ def run_module(): description='Choose the interface where this IP address is currently configured.', ), tlsalpn_acme_ipaddresses=dict( - type='list', required=False, elements='str', + type='list', required=False, elements='str', default=[], description='Enter the all of these IP addresses here.', ), dns_service=dict( diff --git a/scripts/test.sh b/scripts/test.sh index af998c3..64a98f9 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -54,6 +54,8 @@ echo '' run_test 'list' 0 run_test 'reload' 0 run_test 'service' 1 +run_test 'package' 1 +run_test '1_dependencies' 0 run_test 'alias' 1 run_test 'alias_multi' 1 run_test 'alias_purge' 0 @@ -154,12 +156,11 @@ run_test 'dhcrelay_relay' 1 run_test 'dhcp_controlagent' 1 run_test 'dhcp_reservation' 1 run_test 'system' 1 -run_test 'package' 1 run_test 'acme_general' 1 run_test 'acme_account' 1 run_test 'acme_validation' 1 run_test 'acme_action' 1 -run_test 'acme_certificate' 0 +run_test 'acme_certificate' 0 # check mode => dependency on other acme-entries echo '' echo '##############################' diff --git a/scripts/test_cleanup.sh b/scripts/test_cleanup.sh index 05b3b70..01468b1 100755 --- a/scripts/test_cleanup.sh +++ b/scripts/test_cleanup.sh @@ -38,7 +38,7 @@ echo '' echo 'RUNNING CLEANUP' echo '' -ansible-playbook tests/cleanup.yml +ansible-playbook tests/1_cleanup.yml echo '' echo 'FINISHED CLEANUP!' diff --git a/tests/cleanup.yml b/tests/1_cleanup.yml similarity index 92% rename from tests/cleanup.yml rename to tests/1_cleanup.yml index 1012579..0489a6e 100644 --- a/tests/cleanup.yml +++ b/tests/1_cleanup.yml @@ -107,8 +107,7 @@ loop: - 'ANSIBLE_TEST_1_1' - 'ANSIBLE_TEST_1_2' - - 'ANSIBLE_TEST_1_3 -' + - 'ANSIBLE_TEST_1_3' - 'ANSIBLE_TEST_2_1' - name: Cleanup syslog @@ -662,3 +661,58 @@ enabled: false http_host: '127.0.0.1' http_port: 8000 + + - name: Cleanup ACME Certificates + ansibleguy.opnsense.acme_certificate: + description: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_1_2' + - 'ANSIBLE_TEST_1_3' + + - name: Cleanup ACME General + ansibleguy.opnsense.acme_general: + enabled: false + + - name: Cleanup ACME Account + ansibleguy.opnsense.acme_account: + name: '{{ item }}' + state: 'absent' + loop: + - 'ANSIBLE_TEST_1_1' + - 'ANSIBLE_TEST_DUMMY_1_1' + + - name: Cleanup ACME Action + ansibleguy.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 + ansibleguy.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' diff --git a/tests/1_dependencies.yml b/tests/1_dependencies.yml new file mode 100644 index 0000000..94bb9bf --- /dev/null +++ b/tests/1_dependencies.yml @@ -0,0 +1,24 @@ +--- + +- name: Installing Test-Dependencies + hosts: localhost + gather_facts: no + module_defaults: + group/ansibleguy.opnsense.all: + firewall: "{{ lookup('ansible.builtin.env', 'TEST_FIREWALL') }}" + api_credential_file: "{{ lookup('ansible.builtin.env', 'TEST_API_KEY') }}" + ssl_verify: false + + ansibleguy.opnsense.package: + timeout: 120 + + tasks: + - name: Installing packages + ansibleguy.opnsense.package: + name: "{{ item }}" + action: 'install' + loop: + - 'os-squid' + - 'os-frr' + - 'os-bind' + - 'os-acme-client' diff --git a/tests/README.md b/tests/README.md index 809134e..cd79f84 100644 --- a/tests/README.md +++ b/tests/README.md @@ -21,6 +21,9 @@ Some tests need packages to be pre-installed: * webproxy_* - `os-squid` * frr_* - `os-frr` * bind_* - `os-bind` +* acme_* - `os-acme-client` + +See also: `1_dependencies.yml` ### Interfaces diff --git a/tests/acme_certificate.yml b/tests/acme_certificate.yml index 3735044..5235f3b 100644 --- a/tests/acme_certificate.yml +++ b/tests/acme_certificate.yml @@ -314,18 +314,18 @@ - name: Cleanup Dummy Account ansibleguy.opnsense.acme_account: - name: ANSIBLE_TEST_DUMMY_1_1 + name: 'ANSIBLE_TEST_DUMMY_1_1' state: 'absent' diff: false - name: Cleanup Dummy Validation ansibleguy.opnsense.acme_validation: - name: ANSIBLE_TEST_DUMMY_1_1 + name: 'ANSIBLE_TEST_DUMMY_1_1' state: 'absent' diff: false - name: Cleanup Dummy Action ansibleguy.opnsense.acme_action: - name: ANSIBLE_TEST_DUMMY_1_1 + name: 'ANSIBLE_TEST_DUMMY_1_1' state: 'absent' diff: false diff --git a/tests/acme_general.yml b/tests/acme_general.yml index 5e3fa44..15a3da3 100644 --- a/tests/acme_general.yml +++ b/tests/acme_general.yml @@ -61,7 +61,6 @@ restart_timeout: 600 log_level: normal show_intro: true - debug: true register: opn2 failed_when: > opn2.failed or @@ -82,3 +81,7 @@ opn3.failed or opn3.changed when: not ansible_check_mode + + - name: Cleanup + ansibleguy.opnsense.acme_general: + enabled: false From d95dc6a24e2a56e5daa9bd4fdc1546b9a3d69025 Mon Sep 17 00:00:00 2001 From: Rath Pascal Date: Sun, 5 Jan 2025 16:10:25 +0100 Subject: [PATCH 9/9] lint fix --- plugins/module_utils/main/acme_general.py | 2 -- plugins/module_utils/main/acme_validation.py | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/plugins/module_utils/main/acme_general.py b/plugins/module_utils/main/acme_general.py index 09ada5f..42bf3d6 100644 --- a/plugins/module_utils/main/acme_general.py +++ b/plugins/module_utils/main/acme_general.py @@ -2,8 +2,6 @@ from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ Session -from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ - validate_int_fields from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import GeneralModule diff --git a/plugins/module_utils/main/acme_validation.py b/plugins/module_utils/main/acme_validation.py index c847ab3..cea58a9 100644 --- a/plugins/module_utils/main/acme_validation.py +++ b/plugins/module_utils/main/acme_validation.py @@ -2,8 +2,7 @@ from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.api import \ Session -from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import \ - validate_int_fields, is_unset +from ansible_collections.ansibleguy.opnsense.plugins.module_utils.helper.main import is_unset from ansible_collections.ansibleguy.opnsense.plugins.module_utils.base.cls import BaseModule