Use haproxy_vip_binds stanza for Let's Encrypt

Currently Let's Encrypt is using `haproxy_bind_external_lb_vip_address`
to identify naming of resulting certificate which might not match with
expectations, as all other parts of code already do use
`haproxy_vip_binds`
for calculating resulting TLS path.

This patch introduces `type` key for `haproxy_vip_binds` which is used
to identify for which frontends Let's Encrypt certificate should be used
as in most scenarios it's not gonna be issued for "internal" VIPs anyway
due to dns-01 requirement.

Also moving to single "source of truth" for VIP bindings allows to
override and have control over this behaviour.

Change-Id: Id07d9a0ea270d613b37b6adfa373d01a47f7421f
This commit is contained in:
Dmitriy Rabotyagov 2024-11-10 18:23:43 +01:00
parent 7e4ecd10b3
commit 65e53499f5
5 changed files with 20 additions and 33 deletions

View File

@ -290,6 +290,7 @@ haproxy_bind_internal_lb_vip_interface:
# haproxy_vip_binds:
# - address: '*'
# interface: bond0
# type: external
# - address: '192.168.0.10'
# pki_san_records:
# - internal.cloud

View File

@ -222,35 +222,21 @@ Each HAProxy instance will be checking for certbot running on its own
node plus each of the others, and direct any incoming acme-challenge
requests to the HAProxy instance which is performing a renewal.
Domains which will be covered by Let's Encrypt certificate are defined
with ``haproxy_ssl_letsencrypt_domains`` variable, which can be set to
a list. By default certificate will be issued only for
``external_lb_vip_address``.
Another important aspect is defining a list of frontends, for which
issued certificate will be used.
By default, it is goind to be used only for VIPs with type ``external``.
You can control and define type by overriding a variable ``haproxy_vip_binds``.
It is necessary to configure certbot to bind to the HAproxy node local
internal IP address via the haproxy_ssl_letsencrypt_certbot_bind_address
variable in a H/A setup.
Using Certificates from LetsEncrypt (legacy method)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to use `LetsEncrypt SSL Service <https://letsencrypt.org/>`_
you can activate the feature by providing the following configuration in
``/etc/openstack_deploy/user_variables.yml``. Note that this requires
that ``external_lb_vip_address`` in
``/etc/openstack_deploy/openstack_user_config.yml`` is set to the
external DNS address.
.. code-block:: yaml
haproxy_ssl_letsencrypt_enable: true
haproxy_ssl_letsencrypt_email: example@example.com
.. warning::
There is no certificate distribution implementation at this time, so
this will only work for a single haproxy-server environment. The
renewal is automatically handled via CRON and currently will shut
down haproxy briefly during the certificate renewal. The
haproxy shutdown/restart will result in a brief service interruption.
.. _Securing services with SSL certificates: https://docs.openstack.org/project-deploy-guide/openstack-ansible/draft/app-advanced-config-sslcertificates.html
.. _Securing services with SSL certificates: https://docs.openstack.org/openstack-ansible/latest/user/security/index.html
Configuring additional services
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -63,14 +63,14 @@
src: "{{ haproxy_ssl_letsencrypt_config_path }}/{{ haproxy_ssl_letsencrypt_domains | first }}"
dest: >-
{{
haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ (haproxy_bind_external_lb_vip_interface is truthy) | ternary(
item ~ '-' ~ haproxy_bind_external_lb_vip_interface, item) ~ '.pem'
haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ (item.get('interface')) | ternary(
item.get('address') ~ '-' ~ item['interface'], item['address']) ~ '.pem'
}}
regexp: '(privkey|fullchain).pem$'
owner: haproxy
group: haproxy
mode: "0640"
with_items:
- "{{ [haproxy_bind_external_lb_vip_address] + extra_lb_tls_vip_addresses }}"
- "{{ haproxy_vip_binds | selectattr('type', 'defined') | selectattr('type', 'eq', 'external') }}"
notify:
- Reload haproxy

View File

@ -1,9 +1,9 @@
#!/bin/bash
# renew cert if required and copy to haproxy destination
{% for vip in [ haproxy_bind_external_lb_vip_address ] + extra_lb_tls_vip_addresses %}
{% for vip in haproxy_vip_binds | selectattr('type', 'defined') | selectattr('type', 'eq', 'external') %}
cat /etc/letsencrypt/live/{{ haproxy_ssl_letsencrypt_domains | first }}/{fullchain,privkey}.pem \
> {{ haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ vip ~ '.pem' }}
> {{ haproxy_ssl_cert_path ~ '/haproxy_' ~ ansible_facts['hostname'] ~ '-' ~ (vip.get('interface')) | ternary(vip['address'] ~ '-' ~ vip.get('interface'), vip['address']) ~ '.pem' }}
{% endfor %}
systemctl reload haproxy

View File

@ -14,13 +14,13 @@
# limitations under the License.
_haproxy_vip_binds: |
{% set vip_binds = [{'address': haproxy_bind_external_lb_vip_address, 'interface': haproxy_bind_external_lb_vip_interface}] %}
{% set vip_binds = [{'address': haproxy_bind_external_lb_vip_address, 'interface': haproxy_bind_external_lb_vip_interface, 'type': 'external'}] %}
{% if haproxy_bind_internal_lb_vip_address != haproxy_bind_external_lb_vip_address or
haproxy_bind_external_lb_vip_interface != haproxy_bind_internal_lb_vip_interface %}
{% set _ = vip_binds.append({'address': haproxy_bind_internal_lb_vip_address, 'interface': haproxy_bind_internal_lb_vip_interface}) %}
{% set _ = vip_binds.append({'address': haproxy_bind_internal_lb_vip_address, 'interface': haproxy_bind_internal_lb_vip_interface, 'type': 'internal'}) %}
{% endif %}
{% for vip_address in extra_lb_tls_vip_addresses %}
{% set _ = vip_binds.append({'address': vip_address}) %}
{% set _ = vip_binds.append({'address': vip_address, 'type': 'external'}) %}
{% endfor %}
{{ vip_binds }}