--- # Install cert-manager operator and configure LetsEncrypt certificates. # # Installs the Red Hat cert-manager operator via OLM, creates a ClusterIssuer # for LetsEncrypt with DNS-01 challenges via DNS Made Easy, and provisions # certificates for the ingress wildcard and API server. # ------------------------------------------------------------------ # Step 1: Install cert-manager operator via OLM # ------------------------------------------------------------------ - name: Ensure cert-manager-operator namespace exists kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Namespace metadata: name: cert-manager-operator - name: Create OperatorGroup for cert-manager-operator kubernetes.core.k8s: state: present definition: apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: name: cert-manager-operator namespace: cert-manager-operator spec: targetNamespaces: - cert-manager-operator - name: Subscribe to cert-manager operator kubernetes.core.k8s: state: present definition: apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: openshift-cert-manager-operator namespace: cert-manager-operator spec: channel: "{{ sno_deploy_certmanager_channel }}" installPlanApproval: Automatic name: openshift-cert-manager-operator source: "{{ sno_deploy_certmanager_source }}" sourceNamespace: openshift-marketplace # ------------------------------------------------------------------ # Step 2: Wait for cert-manager to be ready # ------------------------------------------------------------------ - name: Wait for cert-manager CRDs to be available kubernetes.core.k8s_info: api_version: apiextensions.k8s.io/v1 kind: CustomResourceDefinition name: certificates.cert-manager.io register: __sno_deploy_certmanager_crd until: __sno_deploy_certmanager_crd.resources | length > 0 retries: "{{ (sno_deploy_certmanager_wait_timeout / 10) | int }}" delay: 10 - name: Wait for cert-manager deployment to be ready kubernetes.core.k8s_info: api_version: apps/v1 kind: Deployment namespace: cert-manager name: cert-manager register: __sno_deploy_certmanager_deploy until: >- __sno_deploy_certmanager_deploy.resources | length > 0 and (__sno_deploy_certmanager_deploy.resources[0].status.readyReplicas | default(0)) >= 1 retries: "{{ (sno_deploy_certmanager_wait_timeout / 10) | int }}" delay: 10 # ------------------------------------------------------------------ # Step 3: Create DNS Made Easy API credentials for DNS-01 challenges # ------------------------------------------------------------------ - name: Create DNS Made Easy API credentials secret kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Secret metadata: name: dme-api-credentials namespace: cert-manager type: Opaque stringData: api-key: "{{ dme_account_key }}" secret-key: "{{ dme_account_secret }}" no_log: true # ------------------------------------------------------------------ # Step 4: Deploy DNS Made Easy webhook solver # ------------------------------------------------------------------ - name: Create webhook namespace kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Namespace metadata: name: cert-manager-webhook-dnsmadeeasy - name: Create webhook ServiceAccount kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: ServiceAccount metadata: name: cert-manager-webhook-dnsmadeeasy namespace: cert-manager-webhook-dnsmadeeasy - name: Create webhook ClusterRole kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cert-manager-webhook-dnsmadeeasy rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "watch"] - apiGroups: ["flowcontrol.apiserver.k8s.io"] resources: ["flowschemas", "prioritylevelconfigurations"] verbs: ["list", "watch"] - name: Create webhook ClusterRoleBinding kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cert-manager-webhook-dnsmadeeasy roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cert-manager-webhook-dnsmadeeasy subjects: - kind: ServiceAccount name: cert-manager-webhook-dnsmadeeasy namespace: cert-manager-webhook-dnsmadeeasy - name: Create auth-delegator ClusterRoleBinding for webhook kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cert-manager-webhook-dnsmadeeasy:auth-delegator roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:auth-delegator subjects: - kind: ServiceAccount name: cert-manager-webhook-dnsmadeeasy namespace: cert-manager-webhook-dnsmadeeasy - name: Create authentication-reader RoleBinding for webhook kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: cert-manager-webhook-dnsmadeeasy:webhook-authentication-reader namespace: kube-system roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: extension-apiserver-authentication-reader subjects: - kind: ServiceAccount name: cert-manager-webhook-dnsmadeeasy namespace: cert-manager-webhook-dnsmadeeasy - name: Create domain-solver ClusterRole for cert-manager kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cert-manager-webhook-dnsmadeeasy:domain-solver rules: - apiGroups: ["{{ sno_deploy_webhook_group_name }}"] resources: ["*"] verbs: ["create"] - name: Bind domain-solver to cert-manager ServiceAccount kubernetes.core.k8s: state: present definition: apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cert-manager-webhook-dnsmadeeasy:domain-solver roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cert-manager-webhook-dnsmadeeasy:domain-solver subjects: - kind: ServiceAccount name: cert-manager namespace: cert-manager - name: Create self-signed Issuer for webhook TLS kubernetes.core.k8s: state: present definition: apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: cert-manager-webhook-dnsmadeeasy-selfsign namespace: cert-manager-webhook-dnsmadeeasy spec: selfSigned: {} - name: Create webhook TLS certificate kubernetes.core.k8s: state: present definition: apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: cert-manager-webhook-dnsmadeeasy-tls namespace: cert-manager-webhook-dnsmadeeasy spec: secretName: cert-manager-webhook-dnsmadeeasy-tls duration: 8760h renewBefore: 720h issuerRef: name: cert-manager-webhook-dnsmadeeasy-selfsign kind: Issuer dnsNames: - cert-manager-webhook-dnsmadeeasy - cert-manager-webhook-dnsmadeeasy.cert-manager-webhook-dnsmadeeasy - cert-manager-webhook-dnsmadeeasy.cert-manager-webhook-dnsmadeeasy.svc - name: Deploy webhook solver kubernetes.core.k8s: state: present definition: apiVersion: apps/v1 kind: Deployment metadata: name: cert-manager-webhook-dnsmadeeasy namespace: cert-manager-webhook-dnsmadeeasy spec: replicas: 1 selector: matchLabels: app: cert-manager-webhook-dnsmadeeasy template: metadata: labels: app: cert-manager-webhook-dnsmadeeasy spec: serviceAccountName: cert-manager-webhook-dnsmadeeasy containers: - name: webhook image: "{{ sno_deploy_webhook_image }}" args: - --tls-cert-file=/tls/tls.crt - --tls-private-key-file=/tls/tls.key - --secure-port=8443 ports: - containerPort: 8443 name: https protocol: TCP env: - name: GROUP_NAME value: "{{ sno_deploy_webhook_group_name }}" livenessProbe: httpGet: path: /healthz port: https scheme: HTTPS initialDelaySeconds: 5 periodSeconds: 10 readinessProbe: httpGet: path: /healthz port: https scheme: HTTPS initialDelaySeconds: 5 periodSeconds: 10 resources: requests: cpu: 10m memory: 32Mi limits: memory: 64Mi volumeMounts: - name: certs mountPath: /tls readOnly: true volumes: - name: certs secret: secretName: cert-manager-webhook-dnsmadeeasy-tls - name: Create webhook Service kubernetes.core.k8s: state: present definition: apiVersion: v1 kind: Service metadata: name: cert-manager-webhook-dnsmadeeasy namespace: cert-manager-webhook-dnsmadeeasy spec: type: ClusterIP ports: - port: 443 targetPort: https protocol: TCP name: https selector: app: cert-manager-webhook-dnsmadeeasy - name: Register webhook APIService kubernetes.core.k8s: state: present definition: apiVersion: apiregistration.k8s.io/v1 kind: APIService metadata: name: "v1alpha1.{{ sno_deploy_webhook_group_name }}" spec: group: "{{ sno_deploy_webhook_group_name }}" groupPriorityMinimum: 1000 versionPriority: 15 service: name: cert-manager-webhook-dnsmadeeasy namespace: cert-manager-webhook-dnsmadeeasy version: v1alpha1 insecureSkipTLSVerify: true - name: Wait for webhook deployment to be ready kubernetes.core.k8s_info: api_version: apps/v1 kind: Deployment namespace: cert-manager-webhook-dnsmadeeasy name: cert-manager-webhook-dnsmadeeasy register: __sno_deploy_webhook_deploy until: >- __sno_deploy_webhook_deploy.resources | length > 0 and (__sno_deploy_webhook_deploy.resources[0].status.readyReplicas | default(0)) >= 1 retries: 30 delay: 10 # ------------------------------------------------------------------ # Step 5: Create ClusterIssuer for LetsEncrypt # ------------------------------------------------------------------ - name: Create LetsEncrypt ClusterIssuer kubernetes.core.k8s: state: present definition: apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-production spec: acme: email: "{{ sno_deploy_letsencrypt_email }}" server: "{{ __sno_deploy_letsencrypt_server_url }}" privateKeySecretRef: name: letsencrypt-production-account-key solvers: - dns01: webhook: groupName: "{{ sno_deploy_webhook_group_name }}" solverName: dnsmadeeasy config: apiKeySecretRef: name: dme-api-credentials key: api-key secretKeySecretRef: name: dme-api-credentials key: secret-key - name: Wait for ClusterIssuer to be ready kubernetes.core.k8s_info: api_version: cert-manager.io/v1 kind: ClusterIssuer name: letsencrypt-production register: __sno_deploy_clusterissuer until: >- __sno_deploy_clusterissuer.resources | length > 0 and (__sno_deploy_clusterissuer.resources[0].status.conditions | default([]) | selectattr('type', '==', 'Ready') | selectattr('status', '==', 'True') | list | length > 0) retries: 12 delay: 10 # ------------------------------------------------------------------ # Step 6: Create Certificate resources # ------------------------------------------------------------------ - name: Create apps wildcard certificate kubernetes.core.k8s: state: present definition: apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: apps-wildcard-cert namespace: openshift-ingress spec: secretName: apps-wildcard-tls issuerRef: name: letsencrypt-production kind: ClusterIssuer dnsNames: - "{{ __sno_deploy_apps_wildcard }}" duration: 2160h renewBefore: 720h - name: Create API server certificate kubernetes.core.k8s: state: present definition: apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: api-server-cert namespace: openshift-config spec: secretName: api-server-tls issuerRef: name: letsencrypt-production kind: ClusterIssuer dnsNames: - "{{ __sno_deploy_api_hostname }}" duration: 2160h renewBefore: 720h # ------------------------------------------------------------------ # Step 7: Wait for certificates to be issued # ------------------------------------------------------------------ - name: Wait for apps wildcard certificate to be ready kubernetes.core.k8s_info: api_version: cert-manager.io/v1 kind: Certificate namespace: openshift-ingress name: apps-wildcard-cert register: __sno_deploy_apps_cert until: >- __sno_deploy_apps_cert.resources | length > 0 and (__sno_deploy_apps_cert.resources[0].status.conditions | default([]) | selectattr('type', '==', 'Ready') | selectattr('status', '==', 'True') | list | length > 0) retries: "{{ (sno_deploy_certificate_wait_timeout / 10) | int }}" delay: 10 - name: Wait for API server certificate to be ready kubernetes.core.k8s_info: api_version: cert-manager.io/v1 kind: Certificate namespace: openshift-config name: api-server-cert register: __sno_deploy_api_cert until: >- __sno_deploy_api_cert.resources | length > 0 and (__sno_deploy_api_cert.resources[0].status.conditions | default([]) | selectattr('type', '==', 'Ready') | selectattr('status', '==', 'True') | list | length > 0) retries: "{{ (sno_deploy_certificate_wait_timeout / 10) | int }}" delay: 10 # ------------------------------------------------------------------ # Step 8: Patch IngressController and APIServer to use the certs # ------------------------------------------------------------------ - name: Patch default IngressController to use LetsEncrypt cert kubernetes.core.k8s: state: present merge_type: merge definition: apiVersion: operator.openshift.io/v1 kind: IngressController metadata: name: default namespace: openshift-ingress-operator spec: defaultCertificate: name: apps-wildcard-tls - name: Patch APIServer to use LetsEncrypt cert kubernetes.core.k8s: state: present merge_type: merge definition: apiVersion: config.openshift.io/v1 kind: APIServer metadata: name: cluster spec: servingCerts: namedCertificates: - names: - "{{ __sno_deploy_api_hostname }}" servingCertificate: name: api-server-tls # ------------------------------------------------------------------ # Step 9: Wait for rollouts # ------------------------------------------------------------------ - name: Wait for API server to begin restart ansible.builtin.pause: seconds: 30 - name: Wait for router pods to restart with new cert kubernetes.core.k8s_info: api_version: apps/v1 kind: Deployment namespace: openshift-ingress name: router-default register: __sno_deploy_router until: >- __sno_deploy_router.resources is defined and __sno_deploy_router.resources | length > 0 and (__sno_deploy_router.resources[0].status.updatedReplicas | default(0)) == (__sno_deploy_router.resources[0].status.replicas | default(1)) and (__sno_deploy_router.resources[0].status.readyReplicas | default(0)) == (__sno_deploy_router.resources[0].status.replicas | default(1)) retries: 60 delay: 10 - name: Display cert-manager configuration summary ansible.builtin.debug: msg: - "cert-manager configuration complete!" - " ClusterIssuer : letsencrypt-production" - " Apps wildcard : {{ __sno_deploy_apps_wildcard }}" - " API cert : {{ __sno_deploy_api_hostname }}" verbosity: 1