From 076bc4e03be105d43e6aedf598393d586039a1a0 Mon Sep 17 00:00:00 2001 From: Alexei Znamensky <103110+russoz@users.noreply.github.com> Date: Fri, 17 Apr 2026 23:56:57 +1200 Subject: [PATCH] etcd3 lookup - improve HTTPS connection handling and docs (#11861) * etcd3 lookup - improve HTTPS connection handling and documentation Improve user experience when connecting to HTTPS etcd3 endpoints: - Strip URL scheme from host option when present, with a warning - Warn when HTTPS endpoint is specified but ca_cert is not provided - Document that ca_cert is required to enable TLS - Add HTTPS connection example - Fix minor doc markup issue in notes section Fixes #1664 Co-Authored-By: Claude Opus 4.6 * Add changelog fragment for PR 11861 Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../fragments/11861-etcd3-lookup-https.yml | 6 +++ plugins/lookup/etcd3.py | 38 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/11861-etcd3-lookup-https.yml diff --git a/changelogs/fragments/11861-etcd3-lookup-https.yml b/changelogs/fragments/11861-etcd3-lookup-https.yml new file mode 100644 index 0000000000..d8c423fc72 --- /dev/null +++ b/changelogs/fragments/11861-etcd3-lookup-https.yml @@ -0,0 +1,6 @@ +bugfixes: + - >- + etcd3 lookup plugin - improve HTTPS endpoint handling by stripping URL schemes from the ``host`` option + and warning when ``ca_cert`` is not provided for HTTPS endpoints + (https://github.com/ansible-collections/community.general/issues/1664, + https://github.com/ansible-collections/community.general/pull/11861). diff --git a/plugins/lookup/etcd3.py b/plugins/lookup/etcd3.py index f38f9a602c..fb8386d827 100644 --- a/plugins/lookup/etcd3.py +++ b/plugins/lookup/etcd3.py @@ -34,6 +34,8 @@ options: V(https://hostname:2379), or V(:) form. - The V(host) part is overwritten by O(host) option, if defined. - The V(port) part is overwritten by O(port) option, if defined. + - Note that specifying V(https://) in the endpoint URL does not by itself enable TLS. To connect to an HTTPS etcd3 + endpoint, you must provide O(ca_cert) (and optionally O(cert_cert) and O(cert_key)). env: - name: ETCDCTL_ENDPOINTS default: '127.0.0.1:2379' @@ -42,6 +44,8 @@ options: description: - Etcd3 listening client host. - Takes precedence over O(endpoints). + - Must be a bare hostname or IP address without a URL scheme. If a V(https://) or V(http://) prefix is present, + it will be stripped automatically. type: str port: description: @@ -51,6 +55,7 @@ options: ca_cert: description: - Etcd3 CA authority. + - Required to enable TLS when connecting to an HTTPS etcd3 endpoint. env: - name: ETCDCTL_CACERT type: str @@ -87,9 +92,11 @@ options: type: str notes: - - O(host) and O(port) options take precedence over (endpoints) option. + - O(host) and O(port) options take precedence over O(endpoints) option. - The recommended way to connect to etcd3 server is using E(ETCDCTL_ENDPOINT) environment variable and keep O(endpoints), O(host), and O(port) unused. + - To connect to an HTTPS etcd3 endpoint, the O(ca_cert) option must be provided. Merely specifying V(https://) in + O(endpoints) is not sufficient to enable TLS. seealso: - module: community.general.etcd3 - plugin: community.general.etcd @@ -115,6 +122,10 @@ EXAMPLES = r""" - name: "connect to etcd3 with a client certificate" ansible.builtin.debug: msg: "{{ lookup('community.general.etcd3', 'foo/bar', cert_cert='/etc/ssl/etcd/client.pem', cert_key='/etc/ssl/etcd/client.key') }}" + +- name: "connect to etcd3 over HTTPS" + ansible.builtin.debug: + msg: "{{ lookup('community.general.etcd3', 'foo/bar', endpoints='https://etcd.example.com:2379', ca_cert='/etc/ssl/etcd/ca.pem') }}" """ RETURN = r""" @@ -185,19 +196,42 @@ class LookupModule(LookupBase): # etcd3 class expects host and port as connection parameters, so endpoints # must be mangled a bit to fit in this scheme. # so here we use a regex to extract server and port + is_https = False match = re.compile( - r"^(https?://)?(?P(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([-_\d\w\.]+))(:(?P\d{1,5}))?/?$" + r"^(?Phttps?://)?(?P(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([-_\d\w\.]+))(:(?P\d{1,5}))?/?$" ).match(self.get_option("endpoints")) if match: if match.group("host"): client_params["host"] = match.group("host") if match.group("port"): client_params["port"] = match.group("port") + if match.group("scheme") and match.group("scheme").startswith("https"): + is_https = True for opt in etcd3_cnx_opts: if self.get_option(opt): client_params[opt] = self.get_option(opt) + # strip URL scheme from host if present + if "host" in client_params: + host_match = re.match(r"^(?Phttps?://)(?P.+)$", client_params["host"]) + if host_match: + display.warning( + f"The host option contained a URL scheme '{host_match.group('scheme')}' which has been removed. " + "Use a bare hostname or IP address for the host option. " + "To enable TLS, provide the ca_cert option." + ) + client_params["host"] = host_match.group("host") + if host_match.group("scheme").startswith("https"): + is_https = True + + if is_https and not self.get_option("ca_cert"): + display.warning( + "An HTTPS endpoint was specified but ca_cert was not provided. " + "The connection will be attempted without TLS. " + "To enable TLS, provide the ca_cert option." + ) + cnx_log = dict(client_params) if "password" in cnx_log: cnx_log["password"] = ""