Skip to content

Instantly share code, notes, and snippets.

@amcginlay
Last active April 5, 2023 17:54
Show Gist options
  • Save amcginlay/c067a452a3b1ca2c0d399cdb28c99794 to your computer and use it in GitHub Desktop.
Save amcginlay/c067a452a3b1ca2c0d399cdb28c99794 to your computer and use it in GitHub Desktop.

TLSPK Venafi Enhanced Issuer with TLSPC and Vault (v2)

NOTE: v2 of this walkthrough minimizes the use of jsctl and explicitly installs js-operator:v0.0.1-alpha.24 (via Helm) which has built in support for the latest version of VenafiEnhancedIssuer/VenafiConnection CRDs.

Terminology:

  • TLSPK: TLS Protect for Kubernetes (previously Jetstack Secure or JSS)
  • TLSPC: TLS Protect Cloud (previously Venafi as a Service or VaaS)
  • TLSP: TLS Protect Data Centre (previously Venafi Trust Protection Platform or TPP)
  • VEI: Venafi Enhanced Issuer (not to be confused with the native cert-manager issuer for Venafi)

cert-manager's native Venafi issuer requires Kubernetes secrets to hold Venafi credentials (e.g. API keys). Ideally you wish to eliminate the use of all secrets as these create a potential attack vector.

With the help of TLSPK js-operator, your goal is to use Venafi credentials stored off-cluster to request certificates from either TLSP or TLSPC.

In this is example your certificate issuance platform will be TLSPC and your credentials will be stored in Hashicorp Vault.

Monitor deployments

It is generally good practice to monitor your Kubernetes objects, ensuring your dependencies are in place (i.e. Running) at the point of need

watch "kubectl get pods,crds -A | grep -v system"

Create KinD cluster

k8s_name=k8s-$(date +"%y%m%d%H%M")
cat <<EOF | kind create cluster --config -
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: ${k8s_name}
nodes:
  - role: control-plane
EOF

Bootstrap TLSPK

We aim to minimize the use of jsctl (WIP). At this point we need it ONLY to download the private image pull secrets, as shown below.

# rm -rf ~/.jsctl/ # (optional)
jsctl auth login
jsctl config set organization gallant-wright # (change org as appropriate)
jsctl registry auth init

Install js-operator

Using Helm (instead of jsctl) means we can install whichever version we want - in this case v0.0.1-alpha.24

kubectl create namespace jetstack-secure

dockerconfigjson=$(mktemp)
cat <<EOF > ${dockerconfigjson}
{"auths":
  {"eu.gcr.io": 
    {"username": "_json_key",
     "email": "auth@jetstack.io",
     "auth": "$(echo "_json_key:$(cat ~/.jsctl/eu.gcr.io--jetstack-secure-enterprise.json)" | base64)"
    }
  }
}
EOF

kubectl -n jetstack-secure create secret docker-registry jse-gcr-creds \
  --from-file .dockerconfigjson=${dockerconfigjson}

helm -n jetstack-secure upgrade -i js-operator \
  oci://eu.gcr.io/jetstack-secure-enterprise/charts/js-operator   \
  --version v0.0.1-alpha.24 \
  --registry-config ${dockerconfigjson} \
  --set images.secret.enabled=true   \
  --set images.secret.name=jse-gcr-creds \
  --wait

Bootstrap js-operator with baseline configuration

Use a single installation manifest to get the basics (including the cert-manager and approver-policy components) up and running.

kubectl create -f - << EOF
apiVersion: operator.jetstack.io/v1alpha1
kind: Installation
metadata:
  creationTimestamp: null
  name: jetstack-secure
spec:
  approverPolicy: {}
  certManager:
    controller:
      replicas: 1
    webhook:
      replicas: 1
  images:
    secret: jse-gcr-creds
EOF

sleep 5 && kubectl -n jetstack-secure wait --for=condition=Available=True --all deployments --timeout=-1s

Install VEI controller

This includes the Venafi(Cluster)Issuer and VenafiConnection CRDs

kubectl patch installation jetstack-secure --type merge --patch-file <(cat <<EOF
spec:
  venafiEnhancedIssuer:
    replicas: 1
EOF
)

sleep 5 && kubectl -n jetstack-secure wait --for=condition=Available=True --all deployments --timeout=-1s

At this point you may choose to update your watch command to monitor new CRD type objects, for example

watch "kubectl get pods,venaficonnections,venaficlusterissuers,certificaterequestpolicies -A | grep -v system"

Create Kubernetes Service Account (for Vault)

The VenafiConnection object you are about to create will use this service account to establish communication with Vault. Observe how this references serviceaccounts/token.

TODO would like to factor out this step somehow. Maybe if the venafi-connection service account was adjusted to support serviceaccounts/token resources, then Vault and the VenafiConnection object could both reference that existing service account instead?

cat <<EOF | kubectl -n jetstack-secure apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tlspc-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: create-tokens-for-tlspc-issuer
rules:
  - apiGroups: [""]
    resources: ["serviceaccounts/token"]
    verbs: ["create"]
    resourceNames: ["tlspc-sa"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: tlspc-issuer-sa-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: create-tokens-for-tlspc-issuer
subjects:
  - kind: ServiceAccount
    name: venafi-connection
    namespace: jetstack-secure
EOF

Install Vault inside cluster

helm repo add hashicorp https://helm.releases.hashicorp.com && helm repo update
helm -n vault upgrade -i vault \
  hashicorp/vault \
  --create-namespace \
  --set "server.dev.enabled=true" \
  --wait
sleep 5 && kubectl -n vault wait -l statefulset.kubernetes.io/pod-name=vault-0 --for=condition=ready pod --timeout=-1s

Enable Vault as a credentials provider for the cluster

Note this references tlspc-sa created previously.

kubectl exec -n vault pods/vault-0 -- \
    vault auth enable -path=tlspc kubernetes

kubectl exec -n vault pods/vault-0 -- \
    vault write auth/tlspc/config kubernetes_host=https://kubernetes.default.svc

kubectl exec -n vault pods/vault-0 -- \
    vault write auth/tlspc/role/tlspc-role \
       role_type=jwt \
       bound_audiences=vault.vault.svc.cluster.local \
       user_claim=sub \
       bound_service_account_names=tlspc-sa \
       bound_service_account_namespaces=jetstack-secure \
       policies=tlspc-policy-readonly \
       ttl=5m

cat <<EOF | kubectl exec -i -n vault pods/vault-0 -- vault policy write tlspc-policy-readonly -
path "secret/data/tlspc-connection/creds" {
  capabilities = ["read"]
}
EOF

Store the TLSPC credentials in Vault

api_key=<TLSPC_API_KEY> # <- get this from https://ui.venafi.cloud/platform-settings/user-preferences

kubectl exec -n vault pods/vault-0 -- vault kv put -mount=secret tlspc-connection/creds api-key=${api_key}

Create your VenafiConnection object (reuseable)

Note how this uses tlspc-sa to pull the TLSPC API key out of Vault.

kubectl patch installation jetstack-secure --type merge --patch-file <(cat <<EOF
spec:
  venafiConnections:
    - name: tlspc-connection
      vaas:
        apiKey:
          - serviceAccountToken:
              name: tlspc-sa
              audiences: [vault.vault.svc.cluster.local]
          - hashicorpVaultOAuth:
              authInputType: OIDC
              role: tlspc-role
              authPath: /v1/auth/tlspc/login
              url: http://vault.vault.svc.cluster.local:8200
          - hashicorpVaultSecret:
              secretPath: /v1/secret/data/tlspc-connection/creds
              fields: ["api-key"]
              url: http://vault.vault.svc.cluster.local:8200
EOF
)

Create your VEI object

Create the issuer object which references the VenafiConnection object you just created. Behind the scenes, this also creates an "open" CertificateRequestPolicy object for the issuer so all CRs will be approved by default. Observe how this depends upon the Default\Default zone in TLSPC. Please adjust to suit.

kubectl patch installation jetstack-secure --type merge --patch-file <(cat <<EOF
spec:
  issuers:
    - clusterScope: true
      name: tlspc-issuer
      venafiEnhancedIssuer:
        venafiConnectionName: tlspc-connection
        zone: Default\Default
EOF
)

Create a certificate

We can test the above works by requesting a certificate.

kubectl create namespace tests
cat << EOF | kubectl -n tests apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: www.vei-demo.com
spec:
  secretName: www-vei-demo-com-tls
  commonName: www.vei-demo.com
  dnsNames:
    - www.vei-demo.com
  issuerRef:
    name: tlspc-issuer
    kind: VenafiClusterIssuer
    group: jetstack.io
EOF

Confirm certificate issuance

Check the CertificateRequest is "Ready".

kubectl -n tests get cr

If it is, inspect the secret.

kubectl -n tests get secret www-vei-demo-com-tls -o 'go-template={{index .data "tls.crt" | base64decode}}' | openssl x509 -noout -text | head -11

Terminate all

kind delete cluster --name ${k8s_name}

Appendix

If you wish to connect your cluster to TLSPK (optional), you may do so as follows

k8s_name_tlspk=$(tr "-" "_" <<< ${k8s_name})
jsctl clusters connect ${k8s_name_tlspk}
# jsctl clusters delete ${k8s_name_tlspk} --force
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment