Issue and approve certificates with Venafi Control Plane
Learn how to configure Venafi TPP, cert-manager, approver-policy-enterprise, and venafi-enhanced-issuer so that application teams can help themselves to SSL certificates which comply with enterprise PKI policy.
Create a Venafi Connection With Vault
Choose this connection method if you would like to store the VaaS or TPP credentials in a centralized HashiCorp Vault instance. This method supports both TLS Protect Cloud (VaaS) and TLS Protect Datacenter (TPP), and requires a Vault instance to store the credentials used to connect to VaaS and TPP.
Now that you've installed all the prerequisite software in your cluster it's time to configure a Venafi Connection.
This job would typically be performed by the platform team.
The following example shows a Venafi Connection resource which uses a Kubernetes ServiceAccount Token to authenticate to HashiCorp Vault which contains the credentials for Venafi TPP.
š venafi-connection-1-vault.yaml
# venafi-connection-1-vault.yamlapiVersion: jetstack.io/v1alpha1kind: VenafiConnectionmetadata:name: application-team-1-connectionnamespace: jetstack-securespec:tpp:url: https://tpp1.example.comaccessToken:- serviceAccountToken:name: application-team-1audiences: ["vault.vault.svc.cluster.local"]- hashicorpVaultOAuth:authInputType: OIDCrole: application-team-1authPath: /v1/auth/example-corp/loginurl: http://vault.vault.svc.cluster.local:8200- hashicorpVaultSecret:secretPath: /v1/secret/data/application-team-1/tpp-username-passwordfields: ["username", "password"]url: http://vault.vault.svc.cluster.local:8200- tppOAuth:authInputType: UsernamePasswordclientId: tls-protect-kubernetes # ā This 'clientId' value has to match the value configured in TPPurl: https://tpp1.example.com
kubectl apply -f venafi-connection-1-vault.yaml
ā ļø The VenafiConnection resource will not report any issues in its status as long as it is not used. Create an Issuer or CertificateRequestPolicy that references the VenafiConnection to start using the VenafiConnection, after which the VenafiConnection resource's status will be updated.
Create an example Issuer to debug our VenafiConnection with
Because the VenafiConnection is not used anywhere yet, its status will remain empty. To figure out if something is wrong with your connection and obtain the error details, we have to start using it. Therefore, we create an example issuer.
š venafi-example-issuer.yaml
# venafi-example-issuer.yamlapiVersion: jetstack.io/v1alpha1kind: VenafiIssuermetadata:name: example-issuernamespace: jetstack-securespec:venafiConnectionName: application-team-1-connectionzone: \VED\Policy\Teams\application-team-1
kubectl apply -f venafi-example-issuer.yaml
Don't forget to remove this example issuer when you are done with all the steps below:
kubectl delete -f venafi-example-issuer.yaml
Create a Role and RoleBinding
If you use kubectl describe to view the status of the VenafiClusterIssuer resource you'll see an error:
$ kubectl -n jetstack-secure describe venaficonnection application-team-1-connection...Status:Ready Conditions:Last Transition Time: 2023-01-10T13:43:38ZMessage: connection is not ready yet (building connection failed): chain element 0 (ServiceAccountToken) error: serviceaccounts "application-team-1" is forbidden: User "system:serviceaccount:jetstack-secure:venafi-connection" cannot create resource "serviceaccounts/token" in API group "" in the namespace "jetstack-secure"Observed Generation: 1Operator Id: venafienhancedissuer.jetstack.ioReason: PendingStatus: False
This tells you that there's a problem with the first item in the accessToken field. The problem is that venafi-enhanced-issuer is trying to request a ServiceAccount token but it doesn't have permission. So first let's add a Role and RoleBinding to venafi-connection.yaml and reapply it:
# venafi-connection-2.yaml# create role that allows creating sa tokens for 'application-team-1'apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata:name: create-tokens-for-application-team-1namespace: jetstack-securerules:- apiGroups: [""]resources: ["serviceaccounts/token"]verbs: ["create"]resourceNames: ["application-team-1"]---# link the controller's service account to the 'create-tokens-for-vault-sa' roleapiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata:name: application-team-1-sa-rolebindingnamespace: jetstack-secureroleRef:apiGroup: rbac.authorization.k8s.iokind: Rolename: create-tokens-for-application-team-1subjects:- kind: ServiceAccountname: venafi-connectionnamespace: jetstack-secure
kubectl apply -f venafi-connection-2.yaml
š” Our controllers use exponential back-off between reconciliations in case a reconciliation error occurs. You can reset the exponential back-off timer by adding an annotation to the VenafiConnection resource, resulting in an immediate reconciliation.
kubectl -n jetstack-secure annotate venaficonnection application-team-1-connection last-trigger=$(date -u +"%FT%T.000Z") --overwrite
Create a ServiceAccount
If you use kubectl describe to view the status of the VenafiClusterIssuer resource again you'll see a different error:
$ kubectl -n jetstack-secure describe venaficonnection application-team-1-connection...Status:Ready Conditions:Last Transition Time: 2023-01-10T13:45:41ZMessage: connection is not ready yet (building connection failed): chain element 0 (ServiceAccountToken) error: serviceaccounts "application-team-1" not foundObserved Generation: 1Operator Id: venafienhancedissuer.jetstack.ioReason: PendingStatus: False
Add a ServiceAccount to venafi-connection.yaml and reapply:
# venafi-connection-3.yaml# create a ServiceAccount which will be used to authenticate to HashiCorp VaultapiVersion: v1kind: ServiceAccountmetadata:name: application-team-1namespace: jetstack-secure
kubectl apply -f venafi-connection-3.yaml
š” Our controllers use exponential back-off between reconciliations in case a reconciliation error occurs. You can reset the exponential back-off timer by adding an annotation to the VenafiConnection resource, resulting in an immediate reconciliation.
kubectl -n jetstack-secure annotate venaficonnection application-team-1-connection last-trigger=$(date -u +"%FT%T.000Z") --overwrite
Install HashiCorp Vault
Once again, run kubectl describe to examine the status of the VenafiClusterIssuer. This time you will see that there is a problem with next element in the authentication chain. It can't connect to the HashiCorp Vault server.
$ kubectl -n jetstack-secure describe venaficonnection application-team-1-connection...Status:Ready Conditions:Last Transition Time: 2023-01-10T13:53:53ZMessage: connection is not ready yet (building connection failed): chain element 1 (HashicorpVaultOAuth) error: Post "http://vault.vault.svc.cluster.local:8200/v1/auth/example-corp/login": dial tcp: lookup vault.vault.svc.cluster.local on 10.96.0.10:53: no such hostObserved Generation: 1Operator Id: venafienhancedissuer.jetstack.ioReason: PendingStatus: False
So now we need to deploy HashiCorp vault. For simplicity we'll install Vault in the Kubernetes cluster in "dev" server mode, but you may already have an existing HashiCorp Vault server that you prefer to use.
ā ļø Never use HashiCorp Vault "dev" server mode in production.
š hashicorp-vault.values.yaml
global:tlsDisable: trueinjector:enabled: falseserver:auditStorage:enabled: falsestandalone:enabled: truedev:enabled: truedevRootToken: root-token-1234ha:enabled: falseui:enabled: trueserviceType: ClusterIPserviceNodePort: nullexternalPort: 8200
helm upgrade vault vault \--install \--create-namespace \--wait \--namespace vault \--repo https://helm.releases.hashicorp.com \--values hashicorp-vault.values.yaml
š” Our controllers use exponential back-off between reconciliations in case a reconciliation error occurs. You can reset the exponential back-off timer by adding an annotation to the VenafiConnection resource, resulting in an immediate reconciliation.
kubectl -n jetstack-secure annotate venaficonnection application-team-1-connection last-trigger=$(date -u +"%FT%T.000Z") --overwrite
Configure HashiCorp Vault
Now we need to configure Vault to allow the application-team-1 ServiceAccount to authenticate.
$ kubectl -n jetstack-secure describe venaficonnection application-team-1-connection...Status:Ready Conditions:Last Transition Time: 2023-01-10T14:00:46ZMessage: connection is not ready yet (building connection failed): chain element 1 (HashicorpVaultOAuth) error: HTTP 403: permission deniedObserved Generation: 1Operator Id: venafienhancedissuer.jetstack.ioReason: PendingStatus: False
ā¹ļø In this tutorial HashiCorp Vault is configured to use Kubernetes authentication, and it is running inside the Kubernetes cluster. Alternatively you can configure HashiCorp Vault to use JWT authentication, which is similar but with some important differences. The disadvantage is that with JWT authentication HashiCorp Vault will not be able to check for revoked Kubernetes JWT tokens. The advantage is that Vault won't need to connect to the Kubernetes API server which makes it easier to connect to a HashiCorp Vault server that is running outside the Kubernetes cluster where venafi-enhanced-issuer is running.
kubectl exec -n vault pods/vault-0 -- \vault auth enable -path=example-corp kuberneteskubectl exec -n vault pods/vault-0 -- \vault write auth/example-corp/config kubernetes_host=https://kubernetes.default.svckubectl exec -n vault pods/vault-0 -- \vault write auth/example-corp/role/application-team-1 \role_type=jwt \bound_audiences=vault.vault.svc.cluster.local \user_claim=sub \bound_service_account_names=application-team-1 \bound_service_account_namespaces=jetstack-secure \policies=application-team-1-readonly \ttl=5m
š” Our controllers use exponential back-off between reconciliations in case a reconciliation error occurs. You can reset the exponential back-off timer by adding an annotation to the VenafiConnection resource, resulting in an immediate reconciliation.
kubectl -n jetstack-secure annotate venaficonnection application-team-1-connection last-trigger=$(date -u +"%FT%T.000Z") --overwrite
Store TPP Credentials in HashiCorp Vault
Now the second step in the authentication chain should be working. But the next step will fail, because we haven't yet saved the Venafi TPP credentials to a secret in vault.
$ kubectl -n jetstack-secure describe venaficonnection application-team-1-connection...Status:Ready Conditions:Last Transition Time: 2023-01-10T14:03:33ZMessage: connection is not ready yet (building connection failed): chain element 2 (HashicorpVaultSecret) error: HTTP 403: 1 error occurred:* permission deniedObserved Generation: 1Operator Id: venafienhancedissuer.jetstack.ioReason: PendingStatus: False
š hashicorp-vault.policy.hcl
path "secret/data/application-team-1/tpp-username-password" {capabilities = ["read"]}
kubectl exec -i -n vault pods/vault-0 -- \vault policy write application-team-1-readonly - < hashicorp-vault.policy.hclkubectl exec -n vault pods/vault-0 -- \vault kv put -mount=secret application-team-1/tpp-username-password username=application-team-1 password=44054994-384d-40e8-adcb-b18cc4d7b756
š” Our controllers use exponential back-off between reconciliations in case a reconciliation error occurs. You can reset the exponential back-off timer by adding an annotation to the VenafiConnection resource, resulting in an immediate reconciliation.
kubectl -n jetstack-secure annotate venaficonnection application-team-1-connection last-trigger=$(date -u +"%FT%T.000Z") --overwrite
Configure Venafi TPP
Assuming that you've configured HashiCorp Vault and added a valid username / password credential to a Vault secret, the issuer should be able to connect to authenticate to Venafi TPP
And assuming that the PolicyDN is correct and the user account has permission to read and write to that policy folder, the example VenafiIssuer resource should be Ready.
$ kubectl -n jetstack-secure get venafiissuer example-issuerNAME READY REASON MESSAGE LASTTRANSITION OBSERVEDGENERATION GENERATION AGEapplication-team-1 True Checked checked 64s 1 1 67s
You may encounter some of the following error messages in the Ready condition, which are described below:
- Username/password combination not valid
Check that you have added the correct username and password to the secret in HashiCorp Vault:
Message: connection is not ready yet (building connection failed): chain element 3 (TPPOAuth) error: HTTP 400: invalid_grant: Username/password combination not validReason: Pending
- Application tls-protect-kubernetes is not registered and cannot obtain grants
Check that you have configured an Application called tls-protect-kubernetes in Venafi TPP.
Message: connection is not ready yet (building connection failed): chain element 3 (TppOAuth) error: unauthorized_client: Application 'tls-protect-kubernetes' is not registered and cannot obtain grantsReason: Pending
- zone not found
Check that the PolicyDN value is correct and that the corresponding policy folder exists in Venafi TPP. Check that the Venafi TPP user account has permission to view, read, write and revoke in that policy folder.
Message: Issuer is not ready yet: client.ReadZoneConfiguration: vcert error: your data contains problems: zone not foundReason: Pending
š” Our controllers use exponential back-off between reconciliations in case a reconciliation error occurs. You can reset the exponential back-off timer by adding an annotation to the VenafiConnection resource, resulting in an immediate reconciliation.
kubectl -n jetstack-secure annotate venaficonnection application-team-1-connection last-trigger=$(date -u +"%FT%T.000Z") --overwrite