Add venafi support (#1247)

Signed-off-by: Mohammed Naser mnaser@vexxhost.com
diff --git a/doc/source/deploy/certificates.rst b/doc/source/deploy/certificates.rst
new file mode 100644
index 0000000..f99d4f0
--- /dev/null
+++ b/doc/source/deploy/certificates.rst
@@ -0,0 +1,319 @@
+============
+Certificates
+============
+
+Atmosphere simplifies all the management of your SSL certificates for all of
+your API endpoints by automatically issuing and renewing certificates for you
+using `cert-manager <https://cert-manager.io>`_.
+
+Types
+=====
+
+There are two different ways of issuing certificates.  It can either issue
+individual certificates for each API endpoint or it can issue a wildcard
+certificate for all of your API endpoints.
+
+Individual Certificates
+-----------------------
+
+If you want to issue individual certificates for each API endpoint, you do not
+need to do anything.  This is the default behavior of Atmosphere.
+
+Wildcard Certificate
+--------------------
+
+If you decide to use wildcard certificates, you can either let the certificate
+manager issue and renew the certificate for you or you can use an existing
+wildcard certificate.
+
+Automatically Issued
+~~~~~~~~~~~~~~~~~~~~
+
+If you want to issue a wildcard certificate for all of your API endpoints using
+the certificate manager, you can configure it by setting the the following variable
+in your inventory:
+
+.. code-block:: yaml
+
+  openstack_helm_ingress_wildcard_domain: cloud.atmosphere.dev
+
+This will issue a wildcard certificate for ``*.cloud.atmosphere.dev`` and all
+subdomains.  The certificate manager will automatically renew the certificate
+before it expires.
+
+Existing Certificate
+~~~~~~~~~~~~~~~~~~~~
+
+If you want to use an existing wildcard certificate without relying on the
+certificate manager issuing and renewing the certificate, you can use the
+following steps:
+
+1. Create a Kubernetes TLS secret using your wildcard certificate, you can refer
+   to the `Kubernetes documentation <https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets>`_
+   for more details.
+
+   .. code-block:: shell
+
+     kubectl -n openstack create secret tls wildcard-certs --key=/path/to/tls.key --cert=/path/to/tls.crt
+
+   .. note::
+
+      If you have a certificate that needs to be combined with an intermediate
+      certificate, you can combine them all to a single file with the certificate
+      first, followed by the intermediate certificate, followed by the root.
+
+2. Update the ``openstack_helm_ingress_secret_name`` to point towards the name
+   of the secret you created in step 1.
+
+   .. code-block:: yaml
+
+     openstack_helm_ingress_secret_name: wildcard-certs
+
+3. Set ``cluster_issuer_type`` to ``none``, this is required for other roles
+   like for example Horizon.
+
+   .. code-block:: yaml
+
+     cluster_issuer_type: none
+
+.. warning::
+
+   If you decide to use an existing wildcard certificate, you will need to
+   manually renew the certificate before it expires.
+
+Issuers
+=======
+
+In order to be able to automatically issue certificates, you need to configure
+an issuer.  There are three different types of issuers that you can use with
+Atmosphere.
+
+.. note::
+
+   If you decide to change the certificate issuer after deployment, you will
+   need to delete the existing certificate secret in order for the new issuer
+   to be able to issue a new certificate.
+
+ACME
+----
+
+Atmosphere uses the `ACME <https://tools.ietf.org/html/rfc8555>`_ protocol by
+default to request certificates from `LetsEncrypt <https://letsencrypt.org>`_.
+
+There are two different challenge types that can be used to verify ownership of
+the domain to issue the certificate using ACME.  Regardless of the challenge
+type, you will need to configure an email address to be used for the ACME
+account.
+
+.. code-block:: yaml
+
+  cluster_issuer_type: acme
+  cluster_issuer_acme_email: user@example.com
+
+If you're running your own internal ACME server, you can configure Atmosphere to
+point towards it by setting the ``cluster_issuer_acme_server`` variable.
+
+.. code-block:: yaml
+
+  cluster_issuer_acme_server: https://acme.example.com
+  cluster_issuer_acme_email: user@example.com
+
+If the ACME server is using certificates signed by a custom CA, you can add the
+following configuration to allow Atmosphere to trust the CA.
+
+.. code-block:: yaml
+
+  cluster_issuer_acme_private_ca: true
+
+HTTP-01 Challenge
+~~~~~~~~~~~~~~~~~
+
+This is configured to work out of the box if your APIs are publicly accessible
+since it uses an HTTP-01 challenge to verify ownership of the domain.  You just
+need to configure an email address.
+
+DNS-01 Challenge
+~~~~~~~~~~~~~~~~
+
+Atmosphere uses the ``HTTP-01`` solver by default, which means that as long as
+your ACME server can reach your API, you don't need to do anything else.
+
+If your ACME server cannot reach your API, you will need to use the ``DNS-01``
+challenges which require you to configure your DNS provider.
+
+RFC2136
+*******
+
+If you have a DNS server that supports RFC2136, you can use it to solve the DNS
+challenges with the following configuration:
+
+.. code-block:: yaml
+
+  cluster_issuer_acme_email: user@example.com
+  cluster_issuer_acme_solver: rfc2136
+  cluster_issuer_acme_rfc2136_nameserver: <NAMESERVER>:<PORT>
+  cluster_issuer_acme_rfc2136_tsig_algorithm: <ALGORITHM>
+  cluster_issuer_acme_rfc2136_tsig_key_name: <KEY_NAME>
+  cluster_issuer_acme_rfc2136_tsig_secret_key: <SECRET_KEY>
+
+Route53
+*******
+
+If you are using Route53 to host the DNS for your domains, you can use the
+following configuration:
+
+.. code-block:: yaml
+
+  cluster_issuer_acme_email: user@example.com
+  cluster_issuer_acme_solver: route53
+  cluster_issuer_acme_route53_region: <REGION>
+  cluster_issuer_acme_route53_hosted_zone_id: <HOSTED_ZONE_ID>
+  cluster_issuer_acme_route53_access_key_id: <AWS_ACCESS_KEY_ID>
+  cluster_issuer_acme_route53_secret_access_key: <AWS_SECRET_ACCESS_KEY>
+
+.. note::
+
+   You'll need to make sure that your AWS credentials have the correct
+   permissions to update the Route53 zone.
+
+GoDaddy
+*******
+
+If you're using GoDaddy for the DNS of your domain, you can use the following
+configuration which depends on
+`cert-manager-webhook-godaddy <https://github.com/snowdrop/godaddy-webhook>`_.
+
+.. code-block:: yaml
+
+  cluster_issuer_acme_email: user@example.com
+  cluster_issuer_acme_solver: godaddy
+  cluster_issuer_acme_godaddy_api_key: <GODADDY_API_KEY>
+  cluster_issuer_acme_godaddy_secret_key: <GODADDY_SECRET_KEY>
+
+Infoblox
+********
+
+If you're using Infoblox for the DNS of your domain, you can use the following
+configuration which depends on
+`cert-manager-webhook-infoblox-wapi <https://github.com/luisico/cert-manager-webhook-infoblox-wapi>`_.
+
+.. code-block:: yaml
+
+  cluster_issuer_acme_email: user@example.com
+  cluster_issuer_acme_solver: infoblox
+  cluster_issuer_acme_infoblox_view: <VIEW>
+  cluster_issuer_acme_infoblox_host: <HOST>
+  cluster_issuer_acme_infoblox_username: <USERNAME>
+  cluster_issuer_acme_infoblox_password: <PASSWORD>
+
+Venafi
+------
+
+Venafi is a commercial certificate authority which can be used with Atmosphere
+to issue certificates.  Regardless of the way that you authenticate to Venafi,
+you will need to configure the issuer.
+
+.. code-block:: yaml
+
+  cluster_issuer_type: venafi
+  cluster_issuer_venafi_zone: <ZONE>
+  cluster_issuer_venafi_tpp_url: <URL>
+  cluster_issuer_venafi_tpp_ca_bundle: |
+    -----BEGIN CERTIFICATE-----
+    MIIDBjCCAe4CCQDQ3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
+    VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
+    ...
+    -----END CERTIFICATE-----
+
+Authentication
+~~~~~~~~~~~~~~
+
+There are two different ways to authenticate to Venafi to issue certificates,
+either using a username and password or using an access token.  The username and
+password method is phased out in newer versions of Venafi, so it is recommended
+to use the access token method.
+
+Username and Password
+*********************
+
+If you are using a username and password to authenticate to Venafi, you can
+configure it with the following variables:
+
+.. code-block:: yaml
+
+  cluster_issuer_venafi_username: <USERNAME>
+  cluster_issuer_venafi_password: <PASSWORD>
+
+Access Token
+************
+
+If you are using an access token to authenticate to Venafi, you can configure it
+with the following variable:
+
+.. code-block:: yaml
+
+  cluster_issuer_venafi_access_token: <ACCESS_TOKEN>
+
+Certificate fields
+~~~~~~~~~~~~~~~~~~
+
+If your Venafi zone is strict about the fields that are required or their
+values, you can use the `cert-manager supported annotations <https://cert-manager.io/docs/usage/ingress/#supported-annotations>`_
+to configure the certificate values.
+
+In order to apply these annotations to all ingresses managed by Atmosphere, you
+can use the ``ingress_global_annotations`` variable in your inventory which will
+apply the annotations to all ingresses.
+
+.. code-block:: yaml
+
+  ingress_global_annotations:
+    cert-manager.io/subject-organizations: VEXXHOST, Inc.
+    cert-manager.io/subject-organizationalunits: Cloud Infrastructure
+    cert-manager.io/subject-localities: Montreal
+    cert-manager.io/subject-provinces: Quebec
+    cert-manager.io/subject-countries: CA
+
+Using Pre-existing CA
+---------------------
+
+If you have an existing CA that you'd like to use with Atmosphere, you can
+configure it by including the certificate and private key:
+
+.. code-block:: yaml
+
+  cluster_issuer_type: ca
+  cluster_issuer_ca_certificate: |
+    -----BEGIN CERTIFICATE-----
+    MIIDBjCCAe4CCQDQ3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
+    VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
+    ...
+    -----END CERTIFICATE-----
+  cluster_issuer_ca_private_key: |
+    -----BEGIN RSA PRIVATE KEY-----
+    MIIEpAIBAAKCAQEAw3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
+    VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
+    ...
+    -----END RSA PRIVATE KEY-----
+
+.. note::
+
+   If your issuer is an intermediate certificate, you will need to ensure that
+   the ``certificate`` key includes the full chain in the correct order of issuer,
+   intermediate(s), then root.
+
+Self-signed Certificates
+------------------------
+
+If you are in an environment which does not have a trusted certificate authority
+and it does not have access to the internet to be able to use LetsEncrypt, you
+can use self-signed certificates by adding the following to your inventory:
+
+.. code-block:: yaml
+
+  cluster_issuer_type: self-signed
+
+.. warning::
+
+   Self-signed certificates are not recommended for production environments.
+   They are only recommended for development and testing environments.
diff --git a/doc/source/deploy/index.rst b/doc/source/deploy/index.rst
index ff9238f..c736b3e 100644
--- a/doc/source/deploy/index.rst
+++ b/doc/source/deploy/index.rst
@@ -15,6 +15,7 @@
 
    inventory
    csi
+   certificates
    cinder
    glance
    neutron
diff --git a/roles/cluster_issuer/README.md b/roles/cluster_issuer/README.md
index 68839d0..d53f45d 100644
--- a/roles/cluster_issuer/README.md
+++ b/roles/cluster_issuer/README.md
@@ -1,135 +1 @@
 # `cluster_issuer`
-
-Atmosphere simplifies all the management of your SSL certificates for all of
-your API endpoints by automatically issuing and renewing certificates for you.
-
-## ACME
-
-Atmosphere uses the [ACME](https://tools.ietf.org/html/rfc8555) protocol by
-default to request certificates from [LetsEncrypt](https://letsencrypt.org/).
-
-This is configured to work out of the box if your APIs are publicly accessible,
-you just need to configure an email address.
-
-```yaml
-cluster_issuer_acme_email: user@example.com
-```
-
-If you're running your own internal ACME server, you can configure Atmosphere to
-point towards it by setting the `cluster_issuer_acme_server` variable.
-
-```yaml
-cluster_issuer_acme_server: https://acme.example.com
-cluster_issuer_acme_email: user@example.com
-```
-
-### DNS-01 challenges
-
-Atmosphere uses the `HTTP-01` solver by default, which means that as long as
-your ACME server can reach your API, you don't need to do anything else.
-
-If your ACME server cannot reach your API, you will need to use the DNS-01
-challenges which require you to configure your DNS provider.
-
-#### RFC2136
-
-If you have DNS server that supports RFC2136, you can use it to solve the DNS
-challenges, you can use the following configuration:
-
-```yaml
-cluster_issuer_acme_email: user@example.com
-cluster_issuer_acme_solver: rfc2136
-cluster_issuer_acme_rfc2136_nameserver: <NAMESERVER>:<PORT>
-cluster_issuer_acme_rfc2136_tsig_algorithm: <ALGORITHM>
-cluster_issuer_acme_rfc2136_tsig_key_name: <KEY_NAME>
-cluster_issuer_acme_rfc2136_tsig_secret_key: <SECRET_KEY>
-```
-
-#### Route53
-
-If you are using Route53 to host the DNS for your domains, you can use the
-following configuration:
-
-```yaml
-cluster_issuer_acme_email: user@example.com
-cluster_issuer_acme_solver: route53
-cluster_issuer_acme_route53_region: <REGION>
-cluster_issuer_acme_route53_hosted_zone_id: <HOSTED_ZONE_ID>
-cluster_issuer_acme_route53_access_key_id: <AWS_ACCESS_KEY_ID>
-cluster_issuer_acme_route53_secret_access_key: <AWS_SECRET_ACCESS_KEY>
-```
-
-!!! note
-
-   You'll need to make sure that your AWS credentials have the correct
-   permissions to update the Route53 zone.
-
-#### Godaddy
-
-If you're using Godaddy for the DNS of your domain, you can use the following
-configuration which depends on
-`cert-manager-webhook-godaddy`[https://github.com/snowdrop/godaddy-webhook].
-
-```yaml
-cluster_issuer_acme_email: user@example.com
-cluster_issuer_acme_solver: godaddy
-cluster_issuer_acme_godaddy_api_key: <GODADDY_API_KEY>
-cluster_issuer_acme_godaddy_secret_key: <GODADDY_SECRET_KEY>
-```
-
-#### Infoblox
-
-If you're using Infoblox for the DNS of your domain, you can use the following
-configuration which depends on
-`cert-manager-webhook-infoblox-wapi`[https://github.com/luisico/cert-manager-webhook-infoblox-wapi].
-
-```yaml
-cluster_issuer_acme_email: user@example.com
-cluster_issuer_acme_solver: infoblox
-cluster_issuer_acme_infoblox_view: <VIEW>
-cluster_issuer_acme_infoblox_host: <HOST>
-cluster_issuer_acme_infoblox_username: <USERNAME>
-cluster_issuer_acme_infoblox_password: <PASSWORD>
-```
-
-## Using pre-existing CA
-
-If you have an existing CA that you'd like to use with Atmosphere, you can
-simply configure it by including the certificate and private key:
-
-```yaml
-cluster_issuer_type: ca
-cluster_issuer_ca_certificate: |
-  -----BEGIN CERTIFICATE-----
-  MIIDBjCCAe4CCQDQ3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-  VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
-  ...
-  -----END CERTIFICATE-----
-cluster_issuer_ca_private_key: |
-  -----BEGIN RSA PRIVATE KEY-----
-  MIIEpAIBAAKCAQEAw3Z0Z2Z0Z0jANBgkqhkiG9w0BAQsFADCBhTELMAkGA1UEBhMC
-  VVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28x
-  ...
-  -----END RSA PRIVATE KEY-----
-```
-
-!!! note
-
-   If your issuer is an intermediate certificate, you will need to ensure that
-   they `certificate` key includes the full chain in the correct order of issuer,
-   intermediate(s) then root.
-
-## Self-signed certificates
-
-If you are in an environment which does not have a trusted certificate authority
-and it does not have access to the internet to be able to use LetsEncrypt, you
-can use self-signed certificates by adding the following to your inventory:
-
-```yaml
-cluster_issuer_type: self-signed
-```
-
-!!! warning
-
-   Self-signed certificates are not recommended for production environments,
-   they are only recommended for development and testing environments.
diff --git a/roles/cluster_issuer/defaults/main.yml b/roles/cluster_issuer/defaults/main.yml
index 15db6a4..28ff024 100644
--- a/roles/cluster_issuer/defaults/main.yml
+++ b/roles/cluster_issuer/defaults/main.yml
@@ -29,6 +29,14 @@
 cluster_issuer_acme_cloudflare_email: "{{ cluster_issuer_acme_email }}"
 #cluster_issuer_acme_cloudflare_api_token: <CLOUDFLARE_API_TOKEN>
 
+cluster_issuer_venafi_secret_name: cert-manager-venafi-credentials
+# cluster_issuer_venafi_access_token:
+# cluster_issuer_venafi_username:
+# cluster_issuer_venafi_password:
+# cluster_issuer_venafi_zone:
+# cluster_issuer_venafi_tpp_url:
+# cluster_issuer_venafi_tpp_ca_bundle:
+
 cluster_issuer_acme_rfc2136_secret_name: cert-manager-issuer-tsig-secret-key
 # cluster_issuer_acme_rfc2136_nameserver: <NAMESERVER>:<PORT>
 # cluster_issuer_acme_rfc2136_tsig_algorithm: <ALGORITHM>
diff --git a/roles/cluster_issuer/tasks/type/venafi/main.yml b/roles/cluster_issuer/tasks/type/venafi/main.yml
new file mode 100644
index 0000000..006012f
--- /dev/null
+++ b/roles/cluster_issuer/tasks/type/venafi/main.yml
@@ -0,0 +1,63 @@
+# Copyright (c) 2024 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Create secret (username/password)
+  when:
+    - cluster_issuer_venafi_username is defined
+    - cluster_issuer_venafi_password is defined
+    - cluster_issuer_venafi_access_token is not defined
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      - apiVersion: v1
+        kind: Secret
+        metadata:
+          name: "{{ cluster_issuer_venafi_secret_name }}"
+          namespace: cert-manager
+        stringData:
+          username: "{{ cluster_issuer_venafi_username }}"
+          password: "{{ cluster_issuer_venafi_password }}"
+
+- name: Create secret (access token)
+  when:
+    - cluster_issuer_venafi_username is not defined
+    - cluster_issuer_venafi_password is not defined
+    - cluster_issuer_venafi_access_token is defined
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      - apiVersion: v1
+        kind: Secret
+        metadata:
+          name: "{{ cluster_issuer_venafi_secret_name }}"
+          namespace: cert-manager
+        stringData:
+          access-token: "{{ cluster_issuer_venafi_access_token }}"
+
+- name: Create ClusterIssuer
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      apiVersion: cert-manager.io/v1
+      kind: ClusterIssuer
+      metadata:
+        name: "{{ cluster_issuer_name }}"
+      spec:
+        venafi:
+          zone: "{{ cluster_issuer_venafi_zone }}"
+          tpp:
+            url: "{{ cluster_issuer_venafi_tpp_url }}"
+            caBundle: "{{ cluster_issuer_venafi_tpp_ca_bundle }}"
+            credentialsRef:
+              name: "{{ cluster_issuer_venafi_secret_name }}"
diff --git a/roles/defaults/vars/main.yml b/roles/defaults/vars/main.yml
index ca37422..1f7517c 100644
--- a/roles/defaults/vars/main.yml
+++ b/roles/defaults/vars/main.yml
@@ -21,10 +21,10 @@
   bootstrap: "registry.atmosphere.dev/library/heat:{{ atmosphere_release }}"
   ceph_config_helper: "registry.atmosphere.dev/library/libvirtd:{{ atmosphere_release }}"
   ceph: quay.io/ceph/ceph:v18.2.1
-  cert_manager_cainjector: quay.io/jetstack/cert-manager-cainjector:v1.7.1
-  cert_manager_cli: quay.io/jetstack/cert-manager-ctl:v1.7.1
-  cert_manager_controller: quay.io/jetstack/cert-manager-controller:v1.7.1
-  cert_manager_webhook: quay.io/jetstack/cert-manager-webhook:v1.7.1
+  cert_manager_cainjector: quay.io/jetstack/cert-manager-cainjector:v1.12.10
+  cert_manager_cli: quay.io/jetstack/cert-manager-ctl:v1.12.10
+  cert_manager_controller: quay.io/jetstack/cert-manager-controller:v1.12.10
+  cert_manager_webhook: quay.io/jetstack/cert-manager-webhook:v1.12.10
   cilium_node: quay.io/cilium/cilium:v1.14.8
   cilium_operator: quay.io/cilium/operator-generic:v1.14.8
   cinder_api: "registry.atmosphere.dev/library/cinder:{{ atmosphere_release }}"
diff --git a/roles/ingress/defaults/main.yml b/roles/ingress/defaults/main.yml
index bbb881a..c224e68 100644
--- a/roles/ingress/defaults/main.yml
+++ b/roles/ingress/defaults/main.yml
@@ -1,5 +1,18 @@
-# List of annotations to apply to the Ingress
-ingress_annotations: {}
+# Copyright (c) 2024 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
 
 ingress_class_name: "{{ atmosphere_ingress_class_name }}"
 
@@ -14,3 +27,16 @@
 # ingress_service_port: number
 
 # ingress_secret_name: string
+
+# List of annotations to apply to the Ingress
+#
+# NOTE(mnaser): These are typically used by the role that calls
+#               the ingress role to pass annotations to the
+#               Ingress resource.  You should use role-specific
+#               variable to pass annotations or use the
+#               ingress_global_annotations variable to apply
+#               annotations to all Ingress resources.
+ingress_annotations: {}
+
+# List of annotations to apply to all Ingress resources
+ingress_global_annotations: {}
diff --git a/roles/ingress/templates/ingress.yml.j2 b/roles/ingress/templates/ingress.yml.j2
index 0e48273..cdc4791 100644
--- a/roles/ingress/templates/ingress.yml.j2
+++ b/roles/ingress/templates/ingress.yml.j2
@@ -3,10 +3,9 @@
 metadata:

   name: {{ ingress_name }}

   namespace: {{ ingress_namespace | default('default') }}

-{% if ingress_annotations %}

   annotations:

-    {{ ingress_annotations | to_nice_yaml | indent(4) }}

-{% endif %}

+    cert-manager.io/common-name: {{ ingress_host }}

+    {{ ingress_annotations | combine(ingress_global_annotations, recursive=True) | to_nice_yaml | indent(4) }}

 spec:

   ingressClassName: {{ ingress_class_name }}

   rules:

diff --git a/roles/openstack_helm_ingress/README.md b/roles/openstack_helm_ingress/README.md
index 9b245af..38a9547 100644
--- a/roles/openstack_helm_ingress/README.md
+++ b/roles/openstack_helm_ingress/README.md
@@ -1,39 +1 @@
 # `openstack_helm_ingress`
-
-## Using wildcard certificates
-
-If you have an existing wildcard certificate to use for all your endpoints
-with Atmosphere, you can simply configure it as follows;
-
-1. Create a Kubernetes TLS secret using your wildcard certificate, you can refer
-   to the [Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets)
-   for more details.
-
-   ```shell
-   kubectl -n openstack create secret tls wildcard-certs --key=/path/to/tls.key --cert=/path/to/tls.crt
-   ```
-
-   > **Note**
-   >
-   > If you have a certificate that needs to be combined with an intermediate
-   > certificate, you can combine them all to a single file with the certificate
-   > first, followed by the intermediate certificate, followed by the root.
-
-2. Update the `openstack_helm_ingress_secret_name` to point towards the name
-   of the secret you created in step 1.
-
-   ```yaml
-   openstack_helm_ingress_secret_name: wildcard-certs
-   ```
-
-3. Set cluster_issuer_type to none, this is required for other roles
-   like for example Horizon.
-
-   ```yaml
-   cluster_issuer_type: none
-   ```
-
-> **Note**
->
-> If you make this change after a deployment, you will need to re-run all of the
-> playbooks in order to update all the `Ingress` resources.