Skip to content

Configuring Approver Policy Enterprise

The default cert-manager installation includes an approve-all policy approver. This approver adds an Approved condition to all the certificate requests that use the built-in cert-manager issuers.

The open-source cert-manager approver-policy project provides an replacement for this approve-all approver. It supports configuring simple policy rules using certificate request policy resources.

The Approver Policy Enterprise approver is the enterprise version of the open-source approver and includes two additional approver plugins. The Venafi plugin can be used to make policy decisions based on the policies defined in your Venafi Control Plane. The Rego plugin adds support for writing custom complex rules using the Rego language.

Certificate request policy resources

Multiple certificate request policy resources can be applicable for a single certificate requests. If the certificate requests doesn't conform to at least one of these applicable policies, its approve condition is set to Denied and no certificate is issued based on the request. If the certificate request conforms to all matching policies, the approve condition is set to Approved and issuance can continue. However, if there are no policies that match the certificate request, the approver does not set any approve condition. This is so another approver can still set the approve condition, allowing multiple concurrent approvers, each approving only the certificate request that it is responsible for.

A certificate request policy has four sections; allowed, constraints, selector, and plugins. For more information, see the configuration section of the approver-policy documentation.

Here is a basic certificate request policy:

apiVersion: policy.cert-manager.io/v1alpha1
kind: CertificateRequestPolicy
metadata:
  name: my-first-policy
spec:
  allowed:
    # The commonName field MUST be present and MUST have this value.
    commonName:
      value: "hello.world"
      required: true
    # The dnsNames field is optional, but if present the values MUST all match one of these.
    dnsNames:
      values: ["*.hello.world", "hello.world"]
      required: false
  constraints:
    # The privateKey must be an RSA key of at least 4096 bits.
    privateKey:
      algorithm: RSA
      minSize: 4096
  selector:
    # Only applies to a CertificateRequest which has the following issuerRef
    issuerRef:
      name: "my-issuer"
      kind: "Issuer"
      group: "cert-manager.io"

This policy states that certificate request resources that use the my-issuer Issuer:

  • MUST have commonName: hello.world.
  • MAY also include the domainNames field and if present the values must be hello.world (or a sub-domain of that).
  • MUST use an RSA private key with size at least 4096 bit.

Binding the certificate request policy to certificate request resources

Certificate request policy resources are only used for the CertificateRequest resources that match its CertificateRequestPolicy.Selector section and that are bound via RBAC bindings.

Configuring certificate request policy's selector section

The CertificateRequestPolicy.Selector section can be set to {}, or contain a IssuerRef field and/ or a Namespace field.

A certificate request policy will only be used to approver/ deny a certificate request if both of these fields match the request's configuration, following the rules described here. Furthermore, the certificate request has to be bound to the certificate request policy via RBAC bindings.

Creating RBAC bindings

Every certificate request contains the following unchanging identity fields:

  • username: the name of the user that created the certificate request (a user account or a Service Account).
  • groups: the group membership of the user at the moment when it was created.

These identity fields are managed by cert-manager and can't be set by a user or Service Account. The following is an example of a certificate request which was created by the kubernetes-admin user:

apiGroup: cert-manager.io/v1
Kind: CertificateRequest
metadata:
  name: my-request
  namespace: default
spec:
  ...
  username: kubernetes-admin
  groups:
  - system:masters
  - system:authenticated

Next is an example of a certificate request which was created by cert-manager for a certificate resource. Notice that the username is that of cert-manager Service Account:

apiVersion: cert-manager.io/v1
kind: CertificateRequest
metadata:
  name: my-request-q9nw4
  namespace: default
spec:
  ...
  username: system:serviceaccount:cert-manager:cert-manager
  groups:
  - system:serviceaccounts
  - system:serviceaccounts:cert-manager
  - system:authenticated
  uid: bae0a607-0ef3-496d-bd57-99b5cae28188
...

You use Role and RoleBinding resources, and their Cluster equivalents (ClusterRoles, ClusterRoleBindings) to bind policies to users and ServiceAccounts.

All policy Roles and ClusterRoles share the same fields except for the name of the Certificate Request Policy the role is targeting, namely:

# These fields do not change.
- apiGroups: ["policy.cert-manager.io"]
  resources: ["certificaterequestpolicies"]
  verbs: ["use"]

The names of the certificate request policy resources for the Role are defined in the resourceNames field.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cert-manager-policy:my-first-policy
rules:
  - apiGroups: ["policy.cert-manager.io"]
    resources: ["certificaterequestpolicies"]
    verbs: ["use"]
    resourceNames: ["my-first-policy", "add-more-policies-if-you-like"]

In the following example, the policy is bound to the system:authenticated group. The request therefore matches all users or Service Accounts who create certificate requests.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cert-manager-policy:my-fist-policy
rules:
  - apiGroups: ["policy.cert-manager.io"]
    resources: ["certificaterequestpolicies"]
    verbs: ["use"]
    # Name of the CertificateRequestPolicies to be used.
    resourceNames: ["my-first-policy"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cert-manager-policy:my-first-policy
roleRef:
  # ClusterRole or Role _must_ be bound to a user for the policy to be considered.
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cert-manager-policy:my-first-policy
subjects:
  # The users who should be bound to the policies defined.
  - kind: Group
    name: system:authenticated
    apiGroup: rbac.authorization.k8s.io

In the case of users creating certificates, the actor who is making the actual certificate request resources is the cert-manager Service Account. For these cases we need to bind to the Service Account instead:

- kind: ServiceAcount
  name: cert-manager
  namspace: venafi

Note

Change the namespace value if cert-manager has been installed in a different namespace. For example, cert-manager or openshift-operators.

Testing bindings

You can check whether a user, group, or ServiceAccount is able to use a certificate request policy by using the following kubectl command.

Tip

Keep this command handy to check your policy is set up as you would expect as you go.

kubectl auth can-i -n $NAMESPACE --as $USER use certificaterequestpolicies/$POLICY_NAME

In this case, you can expect this command to always return OK since the policy is bound to the cluster scope to all authenticated identities.

kubectl auth can-i -n sandbox --as alice use certificaterequestpolicies/my-first-policy

No binding vs denied certificate requests

If a certificate request doesn't match any certificate request policy resource, its approved condition remains unset. This is different from a situation in which one or more certificate request policy resources are applicable for a certificate request but one of them denies the request. In the second case an approval condition is still added, but its status is Denied.

Creating certificates

You now have a basic policy in place, so you can observe certificates being approved and denied. To test this, create certificate request resources using local certificate template files via the cmctl CLI tool.

# cert-deny.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: denied-certificate
spec:
  commonName: world.hello
  dnsNames:
    - "world.hello"
    - "example.world.hello"
  privateKey:
    algorithm: RSA
    size: 2048
  issuerRef:
    name: my-issuer
    kind: Issuer
    group: cert-manager.io
cmctl create certificaterequest denied-certificate --from-certificate-file cert-deny.yaml

Use the following kubectl describe command to see why the certificate request was denied.

kubectl describe certificaterequest denied-certificate
...
  Type     Reason  Age   From                    Message
  ----     ------  ----  ----                    -------
  Warning  Denied  14s   policy.cert-manager.io  No policy approved this request: [my-first-policy: [spec.allowed.commonName.value: Invalid value: "world.hello": hello.world, spec.allowed.dnsNames.values: Invalid value: []string{"world.hello", "example.world.hello"}: *.hello.world, hello.world]]

The following example is for a certificate request which passes the policy created earlier:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: approved-certificate
spec:
  commonName: hello.world
  dnsNames:
    - "hello.world"
    - "example.hello.world"
  privateKey:
    algorithm: RSA
    size: 4096
  issuerRef:
    name: my-issuer
    kind: Issuer
    group: cert-manager.io
cmctl create certificaterequest approved-certificate --from-certificate-file cert-approve.yaml
kubectl describe certificaterequest approved-certificate
...
  Type     Reason    Age                From                    Message
  ----     ------    ----               ----                    -------
  Normal   Approved  5s (x13 over 26s)  policy.cert-manager.io  Approved by CertificateRequestPolicy: "my-first-policy"