Cert-Manager Policy Approver in OpenShift

- Thomas Jungbauer Thomas Jungbauer ( Lastmod: 2025-06-04 ) - 9 min read

image from Cert-Manager Policy Approver in OpenShift

One of the most commonly deployed operators in OpenShift environments is the Cert-Manager Operator. It automates the management of TLS certificates for applications running within the cluster, including their issuance and renewal.

The tool supports a variety of certificate issuers by default, including ACME, Vault, and self-signed certificates. Whenever a certificate is needed, Cert-Manager will automatically create a CertificateRequest resource that contains the details of the certificate. This resource is then processed by the appropriate issuer to generate the actual TLS certificate. The approval process in this case is usually fully automated, meaning that the certificate is issued without any manual intervention.

But what if you want to have more control? What if certificate issuance must follow strict organizational policies, such as requiring a specifc country code or organization name? This is where the CertificateRequestPolicy resource, a resource provided by the Approver Policy, comes into play.

This article walks through configuring the Cert-Manager Approver Policy in OpenShift to enforce granular policies on certificate requests.

Prerequisites

Before you begin, ensure you have the following:

  • OpenShift 4.16 or higher with cluster-admin access

  • Cert-Manager Operator installed

The installation of Cert-Manager itself is discussed in the article: Managing Certificates using GitOps approach.
The Cert-Manager Operator does not currently support the Approver Policy by default. You need to install the Approver Policy manually using a Helm Chart. There is a feature request to include the Approver Policy in the Cert-Manager Operator in the future.

Adding Approver Policy Chart as a dependency

The above Chart contains the necessary resources to deploy the Cert-Manager itself and Cert-Manager Approver Policy in OpenShift. To deploy the Approver Policy alongside Cert-Manager, add it as a dependency in your Chart.yaml:

[...]
  - name: cert-manager-approver-policy
    version: v0.19.0
    repository: https://charts.jetstack.io
[...]

This is the official Helm Chart for the Cert-Manager Approver Policy tool provided by Jetstack. The version used in this example is v0.19.0, but you can use a newer version if available.

Configuration of the Helm Chart

The initial values.yaml file was extended to:

  • include the configuration for the Approver Policy Chart

  • the configuration for a CertificateRequestPolicy object

  • with some specific modifications to the CertManager resource itself

Disabling Cert-Manager’s Auto-Approver

The first step we need to do is to disable the auto-approver of the Cert-Manager. If this is not done, there will be a race condition between the auto-approver and the Approver Policy, which will lead to unexpected results. These changes are done by:

cert-manager:
  certManager:
    enable_patch: true (1)

    unsupportedConfigOverrides:
      controller:
        args:
          - '--controllers=*,-certificaterequests-approver' (2)
1This enables the patching of the CertManager resource, which tells the chart to overwrite (patch) the automatically generated CertManager resource with the custom configuration.
2This disables the auto-approver Controller of the Cert-Manager.
As the name suggests, this is currently an unsupported configuration, but it is necessary to disable the auto-approver for the Cert-Manager. In the future versions of the Cert-Manager, this might change, and the auto-approver might be supported out of the box.

In my case, the full CertManager resource looks like this:

apiVersion: operator.openshift.io/v1alpha1
kind: CertManager
metadata:
  annotations:
  name: cluster
spec:
  controllerConfig:
    overrideArgs: (1)
      - '--dns01-recursive-nameservers-only'
      - '--dns01-recursive-nameservers=ns-362.awsdns-45.com:53,ns-930.awsdns-52.net:53'
  logLevel: Normal
  managementState: Managed
  operatorLogLevel: Normal
  unsupportedConfigOverrides: (2)
    controller:
      args:
        - '--controllers=*,-certificaterequests-approver'
1Settings to support AWS Nameservers for DNS01 challenges.
2This disables the auto-approver Controller of the Cert-Manager.

Configuration of the Approver Policy

The second step is to configure the Approver Policy chart. This chart will deploy the necessary resources, most importantly a Deployment that will start the Pods which will process the CertificateRequestPolicy resources later on.

My configuration for that chart looks like this (some default values are omitted for brevity):

cert-manager-approver-policy:

  crds: (1)
    # This option decides if the CRDs should be installed
    # as part of the Helm installation.
    enabled: true
    # This option makes it so that the "helm.sh/resource-policy": keep
    # annotation is added to the CRD. This will prevent Helm from uninstalling
    # the CRD when the Helm release is uninstalled.
    # WARNING: when the CRDs are removed, all cert-manager-approver-policy custom resources
    # (CertificateRequestPolicy) will be removed too by the garbage collector.
    keep: true

  # Number of replicas of approver-policy to run.
  replicaCount: 1 (2)

  image: (3)
    # Target image repository.
    repository: quay.io/jetstack/cert-manager-approver-policy
    # Kubernetes imagePullPolicy on Deployment.
    pullPolicy: IfNotPresent
    tag: v0.19.0

  app:

    # List of signer names that approver-policy will be given permission to
    # approve and deny. CertificateRequests referencing these signer names can be
    # processed by approver-policy. Defaults to an empty array, allowing approval
    # for all signers.
    approveSignerNames: (4)
      - 'issuers.cert-manager.io/*'
      - 'clusterissuers.cert-manager.io/*'
1This enables the installation of the CRDs that are required for the Approver Policy.
2The number of replicas of the Approver Policy Deployment. In most cases, one replica is enough.
3The image configuration for the Approver Policy. The image is pulled from the Jetstack Quay.io repository.
4The list of signer names that the Approver Policy will be allowed to approve. In this case, it is configured to allow all issuers and clusterissuers.
The approveSignerNames are, if configured, an important setting, especially if you want to add custom (cluster)issuers. In such a case, you need to add the name of the custom issuer to this list. Otherwise the Approver Policy will not be able to approve the CertificateRequests for that issuer.

Creating a CertificateRequestPolicy and Role(Binding)

The final step in our configuration is to define a CertificateRequestPolicy resource that will define the policy for the certificate requests. This resource will be processed by the Approver Policy and will determine if a certificate request is approved or denied based on the defined criteria.

The following example shows a CertificateRequestPolicy that will:

  • Allow certificate requests with any common name, DNS names, IP addresses, URIs, and email addresses.

  • Require DNS names to be set.

  • Require the subject to contain a specific organization (MyOrganization) and country code (AT).

  • Allow usages for server auth and client auth.

  • Set constraints for the certificate duration (1h-24h) and private key algorithm (RSA) and size (2048-4096).

  • Allow all issuers by using an empty selector.

role: cert-manager-policy:global-approver (1)
serviceAccount: cert-manager (2)
cert_manager_Namespace: cert-manager (3)

policies:
  - name: my-approver-policy
    enabled: true

    allowed:

      commonName:
        required: false
        value: "*"
        validations: []
      dnsNames: (4)
        required: true
        values:
          - "*"
        validations: []
      ipAddresses:
        required: false
        values: ["*"]
        validations: []
      uris:
        required: false
        values:
          - "*"
        validations: []
      emailAddresses:
        required: false
        values:
          - "*"
        validations: []

      # isCA: false
      subject:
        organizations: (5)
          required: true
          values:
            - "MyOrganization"
          validations:
            - rule: self.matches("MyOrganization")
              message: Organization must be MyOrganization
        countries:
          required: true
          values:
            - AT
          validations:
            - rule: self.matches("AT")
              message: Country code must be AT
      usages:
        - "server auth"
        - "client auth"

    constraints: (6)
      minDuration: 1h
      maxDuration: 24h
      privateKey:
        algorithm: RSA
        minSize: 2048
        maxSize: 4096

    selector:
      issuerRef: {} (7)
1The role that is used to approve the certificate requests. This role must be created in the OpenShift cluster and must have the necessary permissions to approve certificate requests.
2The service account that is used by the Approver Policy to process the certificate requests. This service account must have the necessary permissions to access the CertificateRequest resources.
3The namespace where the Cert-Manager is deployed. This is usually the cert-manager namespace, but you can change it if you have a different namespace.
4The DNS names are required to be set for the certificate request.
5The subject must contain the organization MyOrganization and the country code AT.
6The constraints for the certificate request, such as the minimum and maximum duration, private key algorithm, and size.
7The selector is empty, which means that the policy applies to all issuers. If you want to limit the policy to specific issuers, you can specify the issuerRef here.

Rendered CertificateRequestPolicy and Role(Binding)

The above configuration will create a CertificateRequestPolicy resource that looks like this:

One important note is about the ClusterRole and ClusterRoleBinding that are created by the Helm Chart. The role looks like the following and is required to allow the Approver Policy to approve certificate requests. This small bit, puzzled me for a while:

rules:
  - verbs:
      - use
    apiGroups:
      - policy.cert-manager.io
    resources:
      - certificaterequestpolicies
    resourceNames:
      - my-approver-policy

With the above configuration we are good to go. The Helm Chart can be deployed to the OpenShift cluster (for example, using Argo CD), and the CertificateRequestPolicy will be created automatically.

A new Pod is running in the cert-manager namespace:

❯ oc get pods -n cert-manager | grep approver
NAME                                            READY   STATUS    RESTARTS   AGE
cert-manager-approver-policy-xxxxx   1/1   Running   0   XXm (1)
1The Pod cert-manager-approver-policy-xxxxx is the Pod that is responsible for processing the CertificateRequestPolicy resources.

Testing the Policy

Test 1 - Valid Certificate Request

Now it is time to test the policy. We need to create a Certificate and monitor the output of our approval pod.

As a reminder, the policy we created requires the following:

  • The subject must contain the organization MyOrganization

  • The subject must contain the country code AT.

  • The keysize must be at least 2048 bits. (max 4096 bits)

  • The duration must be between 1 hour and 24 hours.

  • The usage must be server auth or client auth.

Let’s create this example Certificate in the myproject namespace:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: test-certificate1
  namespace: myproject
spec:
  dnsNames:
    - test1.apps.ocp.aws.ispworld.at
  duration: 24h
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-prod
  privateKey:
    algorithm: RSA
    encoding: PKCS1
    rotationPolicy: Always
  secretName: test1
  subject:
    organizations:
      - MyOrganization
    countries:
      - AT
  usages:
    - server auth

In the log of the Approver Policy Pod, we should see the following output:

time=2025-06-03T16:07:58.656Z level=DEBUG+3 msg="Approved by CertificateRequestPolicy: \"my-approver-policy\"" logger=controller-manager/events type=Normal object="{Kind:CertificateRequest Namespace:myproject Name:test-certificate1-1 [...]}" reason=Approved

This indicates that the CertificateRequest was approved by the Approver Policy. The policy was able to validate the subject, keysize, duration, and usage of the certificate request and approved it accordingly. The certificate has been created successfully in the myproject namespace, and the secret test1 contains the TLS certificate and private key.

❯ oc get secret test1 -n myproject -o yaml
apiVersion: v1
data:
  tls.crt: ...
  tls.key: ...
kind: Secret
metadata:
  labels:
    controller.cert-manager.io/fao: "true"
  name: test1
  namespace: myproject
type: kubernetes.io/tls

Test 2 - Invalid Certificate Request

That was easy, but what happens if we create a CertificateRequest that does not meet the policy requirements? Let’s try to create a Certificate without the required organization or a wrong country code:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: test-certificate2
  namespace: myproject
spec:
  dnsNames:
    - test2.apps.ocp.aws.ispworld.at
  duration: 24h
  issuerRef:
    kind: ClusterIssuer
    name: letsencrypt-prod
  privateKey:
    algorithm: RSA
    encoding: PKCS1
    rotationPolicy: Always
  secretName: test2
  subject: (1)
    countries:
      - XX
  usages:
    - server auth
1The subject does not contain the required organization MyOrganization and the country code is set to XX, which is not allowed by the policy.

This will lead to the following error in the log of the Approver Policy Pod:

time=2025-06-03T16:16:02.233Z level=DEBUG+3 msg="No policy approved this request: [my-approver-policy: [spec.allowed.subject.organizations.required: Required value: true, spec.allowed.subject.countries.values: Invalid value: []string{\"XX\"}: AT, spec.allowed.subject.countries.validations[0]: Invalid value: \"XX\": Country code must be AT]]" logger=controller-manager/events type=Warning object="{Kind:CertificateRequest Namespace:myproject Name:test-certificate2-1 ..." reason=Denied

It complains that the subject does not meet the policy requirements and therefore the CertificateRequest was denied.

Ok we have a policy, but whats next?

The above example shows how the Cert-Manager Approver Policy can be configured and deployed, even if it is not yet supported by the Cert-Manager Operator. However, we only scratched the surface of what is possible with the Approver Policy. You can create more complex policies that include additional validations, such as checking the validity of the DNS names, IP addresses, or URIs. You can also create policies that require specific email addresses or organizational units in the subject.

You can even create fine-grained policies that apply to specific issuers or namespaces by using the selector field in the CertificateRequestPolicy resource. This allows you to create policies that are tailored to your specific requirements and use cases.

The best references can be found here:

Conclusion

The Cert-Manager Approver Policy is a powerful tool that allows you to implement custom policies for certificate requests in OpenShift. It provides a way to control the issuance of TLS certificates based on specific criteria, such as the subject, key size, duration, and usage of the certificate. While not yet officially supported by the Cert-Manager Operator, it can be easily integrated into your OpenShift environment using a Helm Chart. Future support is currently being discussed, and it is expected that the Approver Policy will be included in the Cert-Manager Operator in the future.