feat: add infoblox cert-manager validation
diff --git a/charts/cert-manager-webhook-infoblox-wapi/.helmignore b/charts/cert-manager-webhook-infoblox-wapi/.helmignore
new file mode 100644
index 0000000..f0c1319
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/.helmignore
@@ -0,0 +1,21 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
diff --git a/charts/cert-manager-webhook-infoblox-wapi/Chart.yaml b/charts/cert-manager-webhook-infoblox-wapi/Chart.yaml
new file mode 100644
index 0000000..622c060
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/Chart.yaml
@@ -0,0 +1,18 @@
+apiVersion: v1
+appVersion: 1.5.0
+description: Cert-manager webhook for interacting with InfoBlox WAPI servers
+home: https://github.com/luisico/cert-manager-webhook-infoblox-wapi
+keywords:
+- cert-manager
+- webhook
+- letsencrypt
+- acme
+- dns01
+- infoblox
+maintainers:
+- email: lgracia@rockefeller.edu
+  name: Luis Gracia
+name: cert-manager-webhook-infoblox-wapi
+sources:
+- https://github.com/luisico/cert-manager-webhook-infoblox-wapi
+version: 1.5.2
diff --git a/charts/cert-manager-webhook-infoblox-wapi/templates/NOTES.txt b/charts/cert-manager-webhook-infoblox-wapi/templates/NOTES.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/templates/NOTES.txt
diff --git a/charts/cert-manager-webhook-infoblox-wapi/templates/_helpers.tpl b/charts/cert-manager-webhook-infoblox-wapi/templates/_helpers.tpl
new file mode 100644
index 0000000..508d116
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/templates/_helpers.tpl
@@ -0,0 +1,48 @@
+{{/* vim: set filetype=mustache: */}}
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "webhook.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "webhook.fullname" -}}
+{{- if .Values.fullnameOverride -}}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
+{{- else -}}
+{{- $name := default .Chart.Name .Values.nameOverride -}}
+{{- if contains $name .Release.Name -}}
+{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
+{{- else -}}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+{{- end -}}
+{{- end -}}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "webhook.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{- define "webhook.selfSignedIssuer" -}}
+{{ printf "%s-selfsign" (include "webhook.fullname" .) }}
+{{- end -}}
+
+{{- define "webhook.rootCAIssuer" -}}
+{{ printf "%s-ca" (include "webhook.fullname" .) }}
+{{- end -}}
+
+{{- define "webhook.rootCACertificate" -}}
+{{ printf "%s-ca" (include "webhook.fullname" .) }}
+{{- end -}}
+
+{{- define "webhook.servingCertificate" -}}
+{{ printf "%s-tls" (include "webhook.fullname" .) }}
+{{- end -}}
diff --git a/charts/cert-manager-webhook-infoblox-wapi/templates/apiservice.yaml b/charts/cert-manager-webhook-infoblox-wapi/templates/apiservice.yaml
new file mode 100644
index 0000000..126018d
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/templates/apiservice.yaml
@@ -0,0 +1,19 @@
+apiVersion: apiregistration.k8s.io/v1
+kind: APIService
+metadata:
+  name: v1alpha1.{{ .Values.groupName }}
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+  annotations:
+    cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ include "webhook.servingCertificate" . }}"
+spec:
+  group: {{ .Values.groupName }}
+  groupPriorityMinimum: 1000
+  versionPriority: 15
+  service:
+    name: {{ include "webhook.fullname" . }}
+    namespace: {{ .Release.Namespace }}
+  version: v1alpha1
diff --git a/charts/cert-manager-webhook-infoblox-wapi/templates/deployment.yaml b/charts/cert-manager-webhook-infoblox-wapi/templates/deployment.yaml
new file mode 100644
index 0000000..0a78e67
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/templates/deployment.yaml
@@ -0,0 +1,69 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "webhook.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  replicas: {{ .Values.replicaCount }}
+  selector:
+    matchLabels:
+      app: {{ include "webhook.name" . }}
+      release: {{ .Release.Name }}
+  template:
+    metadata:
+      labels:
+        app: {{ include "webhook.name" . }}
+        release: {{ .Release.Name }}
+    spec:
+      serviceAccountName: {{ include "webhook.fullname" . }}
+      containers:
+        - name: {{ .Chart.Name }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          args:
+            - --tls-cert-file=/tls/tls.crt
+            - --tls-private-key-file=/tls/tls.key
+          env:
+            - name: GROUP_NAME
+              value: {{ .Values.groupName | quote }}
+          ports:
+            - name: https
+              containerPort: 443
+              protocol: TCP
+          livenessProbe:
+            httpGet:
+              scheme: HTTPS
+              path: /healthz
+              port: https
+          readinessProbe:
+            httpGet:
+              scheme: HTTPS
+              path: /healthz
+              port: https
+          volumeMounts:
+            - name: certs
+              mountPath: /tls
+              readOnly: true
+          resources:
+{{ toYaml .Values.resources | indent 12 }}
+      volumes:
+        - name: certs
+          secret:
+            secretName: {{ include "webhook.servingCertificate" . }}
+    {{- with .Values.nodeSelector }}
+      nodeSelector:
+{{ toYaml . | indent 8 }}
+    {{- end }}
+    {{- with .Values.affinity }}
+      affinity:
+{{ toYaml . | indent 8 }}
+    {{- end }}
+    {{- with .Values.tolerations }}
+      tolerations:
+{{ toYaml . | indent 8 }}
+    {{- end }}
diff --git a/charts/cert-manager-webhook-infoblox-wapi/templates/pki.yaml b/charts/cert-manager-webhook-infoblox-wapi/templates/pki.yaml
new file mode 100644
index 0000000..a5125d5
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/templates/pki.yaml
@@ -0,0 +1,76 @@
+---
+# Create a selfsigned Issuer, in order to create a root CA certificate for
+# signing webhook serving certificates
+apiVersion: cert-manager.io/v1
+kind: Issuer
+metadata:
+  name: {{ include "webhook.selfSignedIssuer" . }}
+  namespace: {{ .Release.Namespace | quote }}
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  selfSigned: {}
+
+---
+
+# Generate a CA Certificate used to sign certificates for the webhook
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+  name: {{ include "webhook.rootCACertificate" . }}
+  namespace: {{ .Release.Namespace | quote }}
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  secretName: {{ include "webhook.rootCACertificate" . }}
+  duration: {{ .Values.rootCACertificate.duration }}
+  issuerRef:
+    name: {{ include "webhook.selfSignedIssuer" . }}
+  commonName: "ca.webhook.cert-manager"
+  isCA: true
+
+---
+
+# Create an Issuer that uses the above generated CA certificate to issue certs
+apiVersion: cert-manager.io/v1
+kind: Issuer
+metadata:
+  name: {{ include "webhook.rootCAIssuer" . }}
+  namespace: {{ .Release.Namespace | quote }}
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  ca:
+    secretName: {{ include "webhook.rootCACertificate" . }}
+
+---
+
+# Finally, generate a serving certificate for the webhook to use
+apiVersion: cert-manager.io/v1
+kind: Certificate
+metadata:
+  name: {{ include "webhook.servingCertificate" . }}
+  namespace: {{ .Release.Namespace | quote }}
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  secretName: {{ include "webhook.servingCertificate" . }}
+  duration: {{ .Values.servingCertificate.duration }}
+  issuerRef:
+    name: {{ include "webhook.rootCAIssuer" . }}
+  dnsNames:
+  - {{ include "webhook.fullname" . }}
+  - {{ include "webhook.fullname" . }}.{{ .Release.Namespace }}
+  - {{ include "webhook.fullname" . }}.{{ .Release.Namespace }}.svc
diff --git a/charts/cert-manager-webhook-infoblox-wapi/templates/rbac.yaml b/charts/cert-manager-webhook-infoblox-wapi/templates/rbac.yaml
new file mode 100644
index 0000000..a94c273
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/templates/rbac.yaml
@@ -0,0 +1,129 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "webhook.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+---
+# Grant the webhook permission to read the ConfigMap containing the Kubernetes
+# apiserver's requestheader-ca-certificate.
+# This ConfigMap is automatically created by the Kubernetes apiserver.
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: {{ include "webhook.fullname" . }}:webhook-authentication-reader
+  namespace: kube-system
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: extension-apiserver-authentication-reader
+subjects:
+  - apiGroup: ""
+    kind: ServiceAccount
+    name: {{ include "webhook.fullname" . }}
+    namespace: {{ .Release.Namespace }}
+---
+# apiserver gets the auth-delegator role to delegate auth decisions to
+# the core apiserver
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: {{ include "webhook.fullname" . }}:auth-delegator
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: system:auth-delegator
+subjects:
+  - apiGroup: ""
+    kind: ServiceAccount
+    name: {{ include "webhook.fullname" . }}
+    namespace: {{ .Release.Namespace }}
+---
+# Grant cert-manager permission to validate using our apiserver
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: {{ include "webhook.fullname" . }}:domain-solver
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+rules:
+  - apiGroups:
+      - {{ .Values.groupName }}
+    resources:
+      - '*'
+    verbs:
+      - 'create'
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: {{ include "webhook.fullname" . }}:domain-solver
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: {{ include "webhook.fullname" . }}:domain-solver
+subjects:
+  - apiGroup: ""
+    kind: ServiceAccount
+    name: {{ .Values.certManager.serviceAccountName }}
+    namespace: {{ .Values.certManager.namespace }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: {{ include "webhook.fullname" . }}:flowcontrol-solver
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+rules:
+  - apiGroups:
+      - "flowcontrol.apiserver.k8s.io"
+    resources:
+      - 'prioritylevelconfigurations'
+      - 'flowschemas'
+    verbs:
+      - 'list'
+      - 'watch'
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: {{ include "webhook.fullname" . }}:flowcontrol-solver
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: {{ include "webhook.fullname" . }}:flowcontrol-solver
+subjects:
+  - apiGroup: ""
+    kind: ServiceAccount
+    name: {{ include "webhook.fullname" . }}
+    namespace: {{ .Release.Namespace | quote }}
diff --git a/charts/cert-manager-webhook-infoblox-wapi/templates/service.yaml b/charts/cert-manager-webhook-infoblox-wapi/templates/service.yaml
new file mode 100644
index 0000000..f24405e
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/templates/service.yaml
@@ -0,0 +1,20 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "webhook.fullname" . }}
+  namespace: {{ .Release.Namespace }}
+  labels:
+    app: {{ include "webhook.name" . }}
+    chart: {{ include "webhook.chart" . }}
+    release: {{ .Release.Name }}
+    heritage: {{ .Release.Service }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: https
+      protocol: TCP
+      name: https
+  selector:
+    app: {{ include "webhook.name" . }}
+    release: {{ .Release.Name }}
diff --git a/charts/cert-manager-webhook-infoblox-wapi/values.yaml b/charts/cert-manager-webhook-infoblox-wapi/values.yaml
new file mode 100644
index 0000000..c2ca67e
--- /dev/null
+++ b/charts/cert-manager-webhook-infoblox-wapi/values.yaml
@@ -0,0 +1,48 @@
+# The GroupName here is used to identify your company or business unit that
+# created this webhook.
+# For example, this may be "acme.mycompany.com".
+# This name will need to be referenced in each Issuer's `webhook` stanza to
+# inform cert-manager of where to send ChallengePayload resources in order to
+# solve the DNS01 challenge.
+# This group name should be **unique**, hence using your own company's domain
+# here is recommended.
+groupName: acme.mycompany.com
+
+certManager:
+  namespace: cert-manager
+  serviceAccountName: cert-manager
+
+rootCACertificate:
+  duration: "43800h"
+servingCertificate:
+  duration: "8760h"
+
+image:
+  repository: ghcr.io/luisico/cert-manager-webhook-infoblox-wapi
+  tag: 1.5
+  pullPolicy: IfNotPresent
+
+nameOverride: ""
+fullnameOverride: ""
+
+service:
+  type: ClusterIP
+  port: 443
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #  cpu: 100m
+  #  memory: 128Mi
+  # requests:
+  #  cpu: 100m
+  #  memory: 128Mi
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}
diff --git a/hack/sync-charts.sh b/hack/sync-charts.sh
index 892b068..f67c909 100755
--- a/hack/sync-charts.sh
+++ b/hack/sync-charts.sh
@@ -62,6 +62,10 @@
 curl -sL https://charts.jetstack.io/charts/cert-manager-${CERT_MANAGER_VERSION}.tgz \
   | tar -xz -C ${ATMOSPHERE}/charts
 
+CERT_MANAGER_WEBHOOK_INFOBLOX_WAPI_VERSION=1.5.2
+curl -sL https://github.com/luisico/cert-manager-webhook-infoblox-wapi/releases/download/helm-chart-${CERT_MANAGER_WEBHOOK_INFOBLOX_WAPI_VERSION}/cert-manager-webhook-infoblox-wapi-${CERT_MANAGER_WEBHOOK_INFOBLOX_WAPI_VERSION}.tgz \
+  | tar -xz -C ${ATMOSPHERE}/charts
+
 RABBITMQ_CLUSTER_OPERATOR_VERSION=2.6.6
 curl -sL https://charts.bitnami.com/bitnami/rabbitmq-cluster-operator-${RABBITMQ_CLUSTER_OPERATOR_VERSION}.tgz \
   | tar -xz -C ${ATMOSPHERE}/charts
diff --git a/roles/cluster_issuer/README.md b/roles/cluster_issuer/README.md
index fe9259c..6ec4821 100644
--- a/roles/cluster_issuer/README.md
+++ b/roles/cluster_issuer/README.md
@@ -64,6 +64,21 @@
    You'll need to make sure that your AWS credentials have the correct
    permissions to update the Route53 zone.
 
+#### 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
diff --git a/roles/cluster_issuer/defaults/main.yml b/roles/cluster_issuer/defaults/main.yml
index b7bcfb9..ab9bfd4 100644
--- a/roles/cluster_issuer/defaults/main.yml
+++ b/roles/cluster_issuer/defaults/main.yml
@@ -35,6 +35,22 @@
 # cluster_issuer_acme_route53_access_key_id: <AWS_ACCESS_KEY_ID>
 # cluster_issuer_acme_route53_secret_access_key: <AWS_SECRET_ACCESS_KEY>
 
+cluster_issuer_acme_infoblox_helm_release_name: cert-manager-webhook-infoblox-wapi
+cluster_issuer_acme_infoblox_helm_chart_path: "{{ role_path }}/../../charts/cert-manager-webhook-infoblox-wapi/"
+cluster_issuer_acme_infoblox_helm_chart_ref: /usr/local/src/cert-manager-webhook-infoblox-wapi
+cluster_issuer_acme_infoblox_helm_release_namespace: cert-manager
+cluster_issuer_acme_infoblox_helm_values: {}
+
+cluster_issuer_acme_infoblox_group_name: infoblox.cert-manager.atmosphere.dev
+cluster_issuer_acme_infoblox_secret_name: cert-manager-issuer-infoblox-credentials
+cluster_issuer_acme_infoblox_role_name: webhook-infoblox-wapi:secret-reader
+cluster_issuer_acme_infoblox_role_binding_name: "{{ cluster_issuer_acme_infoblox_role_name }}"
+cluster_issuer_acme_infoblox_service_account_name: "{{ cluster_issuer_acme_infoblox_helm_release_name }}"
+# cluster_issuer_acme_infoblox_view: <VIEW>
+# cluster_issuer_acme_infoblox_host: <HOST>
+# cluster_issuer_acme_infoblox_username: <USERNAME>
+# cluster_issuer_acme_infoblox_password: <PASSWORD>
+
 cluster_issuer_ca_secret_name: cert-manager-issuer-ca
 # cluster_issuer_ca_certificate: |
 #   -----BEGIN CERTIFICATE-----
diff --git a/roles/cluster_issuer/tasks/type/acme/solver/infoblox.yml b/roles/cluster_issuer/tasks/type/acme/solver/infoblox.yml
new file mode 100644
index 0000000..181ccc7
--- /dev/null
+++ b/roles/cluster_issuer/tasks/type/acme/solver/infoblox.yml
@@ -0,0 +1,96 @@
+# Copyright (c) 2023 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: Upload Helm chart
+  run_once: true
+  ansible.builtin.import_role:
+    name: upload_helm_chart
+  vars:
+    upload_helm_chart_src: "{{ cluster_issuer_acme_infoblox_helm_chart_path }}"
+    upload_helm_chart_dest: "{{ cluster_issuer_acme_infoblox_helm_chart_ref }}"
+
+- name: Deploy Helm chart
+  run_once: true
+  kubernetes.core.helm:
+    name: "{{ cluster_issuer_acme_infoblox_helm_release_name }}"
+    chart_ref: "{{ cluster_issuer_acme_infoblox_helm_chart_ref }}"
+    release_namespace: "{{ cluster_issuer_acme_infoblox_helm_release_namespace }}"
+    create_namespace: true
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _cluster_issuer_acme_infoblox_helm_values | combine(cluster_issuer_acme_infoblox_helm_values, recursive=True) }}"
+
+- name: Apply manifests for Infoblox integration
+  run_once: true
+  kubernetes.core.k8s:
+    state: present
+    definition:
+      - apiVersion: v1
+        kind: Secret
+        metadata:
+          name: "{{ cluster_issuer_acme_infoblox_secret_name }}"
+          namespace: "{{ cluster_issuer_acme_infoblox_helm_release_namespace }}"
+        type: Opaque
+        stringData:
+          username: "{{ cluster_issuer_acme_infoblox_username }}"
+          password: "{{ cluster_issuer_acme_infoblox_password }}"
+
+      - apiVersion: rbac.authorization.k8s.io/v1
+        kind: Role
+        metadata:
+          name: "{{ cluster_issuer_acme_infoblox_role_name }}"
+          namespace: "{{ cluster_issuer_acme_infoblox_helm_release_namespace }}"
+        rules:
+          - apiGroups: [""]
+            resources: ["secrets"]
+            verbs: ["get", "watch"]
+            resourceNames: ["{{ cluster_issuer_acme_infoblox_secret_name }}"]
+
+      - apiVersion: rbac.authorization.k8s.io/v1
+        kind: RoleBinding
+        metadata:
+          name: "{{ cluster_issuer_acme_infoblox_role_binding_name }}"
+          namespace: "{{ cluster_issuer_acme_infoblox_helm_release_namespace }}"
+        roleRef:
+          apiGroup: rbac.authorization.k8s.io
+          kind: Role
+          name: "{{ cluster_issuer_acme_infoblox_role_name }}"
+        subjects:
+          - kind: ServiceAccount
+            name: "{{ cluster_issuer_acme_infoblox_service_account_name }}"
+            namespace: "{{ cluster_issuer_acme_infoblox_helm_release_namespace }}"
+
+      - apiVersion: cert-manager.io/v1
+        kind: ClusterIssuer
+        metadata:
+          name: "{{ cluster_issuer_name }}"
+        spec:
+          acme:
+            email: "{{ cluster_issuer_acme_email }}"
+            server: "{{ cluster_issuer_acme_server }}"
+            privateKeySecretRef:
+              name: "{{ cluster_issuer_acme_private_key_secret_name }}"
+            solvers:
+              - dns01:
+                  webhook:
+                    groupName: "{{ cluster_issuer_acme_infoblox_group_name }}"
+                    solverName: infoblox-wapi
+                    config:
+                      host: "{{ cluster_issuer_acme_infoblox_host }}"
+                      view: "{{ cluster_issuer_acme_infoblox_view }}"
+                      usernameSecretRef:
+                        name: "{{ cluster_issuer_acme_infoblox_secret_name }}"
+                        key: username
+                      passwordSecretRef:
+                        name: "{{ cluster_issuer_acme_infoblox_secret_name }}"
+                        key: password
diff --git a/roles/cluster_issuer/vars/main.yml b/roles/cluster_issuer/vars/main.yml
new file mode 100644
index 0000000..278e189
--- /dev/null
+++ b/roles/cluster_issuer/vars/main.yml
@@ -0,0 +1,16 @@
+# Copyright (c) 2023 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.
+
+_cluster_issuer_acme_infoblox_helm_values:
+  groupName: "{{ cluster_issuer_acme_infoblox_group_name }}"