<p>I've a working ValidatingWebhookConfiguration and have been creating|approving CSRs with <code>certificates.k8s.io/v1beta</code>.</p>
<p>I upgraded (MicroK8s) from 1.18 to 1.20 and received a warning that <code>certificates.k8s.io/v1beta</code> is deprecated 1.19+ and thought I'd try (without success) upgrading to <code>certificates.k8s.io/v1</code>.</p>
<p>Existing (working) CSR:</p>
<pre><code>apiVersion: certificates.k8s.io/v1beta1
kind: CertificateSigningRequest
metadata:
  name: ${SERVICE}.${NAMESPACE}
spec:
  groups:
  - system:authenticated
  request: $(cat ${FILENAME}.csr | base64 | tr -d '\n')
  usages:
  - digital signature
  - key encipherment
  - server auth
</code></pre>
<p>Upgrading the API generated an error:</p>
<pre><code>missing required field &quot;signerName&quot; in io.k8s.api.certificates.v1.CertificateSigningRequestSpec;
</code></pre>
<p>I read the CSR doc specifically the part about <a href="https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#kubernetes-signers" rel="nofollow noreferrer">Kubernetes signers</a> and, because my existing spec uses <code>server auth</code>, assumed I could use <code>kubernetes.io/kubelet-serving</code> since this is the only one that permits <code>server auth</code>.</p>
<pre><code>apiVersion: certificates.k8s.io/v1                      &lt;&lt;--- UPGRADED
kind: CertificateSigningRequest
metadata:
  name: ${SERVICE}.${NAMESPACE}
spec:
  groups:
  - system:authenticated
  request: $(cat ${FILENAME}.csr | base64 | tr -d '\n')
  signerName: kubernetes.io/kubelet-serving              &lt;&lt;--- ADDED
  usages:
  - digital signature
  - key encipherment
  - server auth
</code></pre>
<p>However, I get errors trying to approve the CSR (as a cluster admin):</p>
<pre><code>kubectl certificate approve ${SERVICE}.${NAMESPACE}
certificatesigningrequest.certificates.k8s.io/${SERVICE}.${NAMESPACE} approved

kubectl get csr ${SERVICE}.${NAMESPACE}
NAME                      SIGNERNAME                      REQUESTOR   CONDITION
${SERVICE}.${NAMESPACE}   kubernetes.io/kubelet-serving   admin       Approved,Failed
</code></pre>
<blockquote>
<p><strong>NOTE</strong> <code>Approved</code> but <code>Failed</code></p>
</blockquote>
<p>And I'm unable to get the certificate (presumably because it <code>Failed</code>):</p>
<pre><code>kubectl get csr ${SERVICE}.${NAMESPACE} \
--output=jsonpath='{.status.certificate}'
</code></pre>
<p>How should I use the <code>certificates.k8s.io/v1</code> API?</p>
<h3>Update: 2021-01-06</h3>
<p>OK, so I realized I have more information on the &quot;Failed&quot; and this gives me something to investigate...</p>
<pre class="lang-sh prettyprint-override"><code>kubectl get csr/${SERVICE}.${NAMESPACE} \
--output=jsonpath=&quot;{.status}&quot; \
| jq .
</code></pre>
<p>Yields:</p>
<pre><code>{
  &quot;conditions&quot;: [
    {
      &quot;lastTransitionTime&quot;: &quot;2021-01-06T18:52:15Z&quot;,
      &quot;lastUpdateTime&quot;: &quot;2021-01-06T18:52:15Z&quot;,
      &quot;message&quot;: &quot;This CSR was approved by kubectl certificate approve.&quot;,
      &quot;reason&quot;: &quot;KubectlApprove&quot;,
      &quot;status&quot;: &quot;True&quot;,
      &quot;type&quot;: &quot;Approved&quot;
    },
    {
      &quot;lastTransitionTime&quot;: &quot;2021-01-06T18:52:15Z&quot;,
      &quot;lastUpdateTime&quot;: &quot;2021-01-06T18:52:15Z&quot;,
      &quot;message&quot;: &quot;subject organization is not system:nodes&quot;,
      &quot;reason&quot;: &quot;SignerValidationFailure&quot;,
      &quot;status&quot;: &quot;True&quot;,
      &quot;type&quot;: &quot;Failed&quot;
    }
  ]
}
</code></pre>
<h3>Update: 2021-01-07</h3>
<p>Thanks @PjoterS</p>
<pre class="lang-sh prettyprint-override"><code>ubectl describe csr/${SERVICE}.${NAMESPACE}
Name:         eldlund.utopial
Labels:       &lt;none&gt;
Annotations:  kubectl.kubernetes.io/last-applied-configuration={&quot;apiVersion&quot;:&quot;certificates.k8s.io/v1&quot;,&quot;kind&quot;:&quot;CertificateSigningRequest&quot;,&quot;metadata&quot;:{&quot;annotations&quot;:{},&quot;name&quot;:&quot;eldlund.utopial&quot;},&quot;spec&quot;:{&quot;groups&quot;:[&quot;system:authenticated&quot;],&quot;request&quot;:&quot;LS0tLS1C...LS0tLS0K&quot;,&quot;signerName&quot;:&quot;kubernetes.io/kubelet-serving&quot;,&quot;usages&quot;:[&quot;digital signature&quot;,&quot;key encipherment&quot;,&quot;server auth&quot;]}}

CreationTimestamp:  Thu, 07 Jan 2021 17:03:23 +0000
Requesting User:    admin
Signer:             kubernetes.io/kubelet-serving
Status:             Pending
Subject:
  Common Name:    eldlund.utopial.svc
  Serial Number:  
Subject Alternative Names:
         DNS Names:  eldlund.utopial.svc
                     eldlund.utopial.svc.cluster.local
Events:  &lt;none&gt;
</code></pre>
<h3>Signing w/ OpenSSL (rather than Kubernetes)</h3>
<p>I tried creating CA crt|key and then a service key|CSR and signing the service CSR with the CA but Kubernetes complains:</p>
<pre><code>x509: certificate is not valid for any names, but wanted to match ainsley.utopial.svc
</code></pre>
<p>Yet the certificate appears to contain both CN and SAN entries:</p>
<p><strong>DOESN'T WORK</strong></p>
<pre class="lang-sh prettyprint-override"><code>openssl x509 -in ${FILENAME}.crt --noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            6f:14:25:8c:...
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = Validating Webhook CA
        Validity
            Not Before: Jan  7 18:10:50 2021 GMT
            Not After : Feb  6 18:10:50 2021 GMT
        Subject: CN = ainsley.utopial.svc
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:ca:56:15:...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                DNS:ainsley.utopial.svc, DNS:ainsley.utopial.svc.cluster.local
    Signature Algorithm: sha256WithRSAEncryption
         b2:ec:22:b6:...
</code></pre>
<blockquote>
<p><strong>NOTE</strong> <code>CN</code> is a DNS name above but an IP below ???</p>
</blockquote>
<p>Reverting to my working solution with <code>v1beta1</code> and changing the service name for completeness (<code>loi</code>), the Webhook succeeds and the certificate appears to be no different from the one shown above (except the different service name):</p>
<p><strong>WORKS</strong></p>
<pre class="lang-sh prettyprint-override"><code>Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            ff:b3:cb:11:...
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = 10.152.183.1
        Validity
            Not Before: Jan  7 18:18:45 2021 GMT
            Not After : Jan  7 18:18:45 2022 GMT
        Subject: CN = loi.utopial.svc
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:d2:cc:c2:...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Server Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Authority Key Identifier: 
                keyid:E7:AE:3A:25:95:D2:F7:5B:C6:EA:50:56:07:E8:25:83:60:88:68:7A

            X509v3 Subject Alternative Name: 
                DNS:loi.utopial.svc, DNS:loi.utopial.svc.cluster.local
    Signature Algorithm: sha256WithRSAEncryption
         48:a1:b2:e2:...
</code></pre>

<p><strong>Update</strong> Switched to <a href="https://cert-manager.io" rel="nofollow noreferrer">cert-manager</a> and everything working <a href="https://pretired.dazwilkin.com/posts/210108/" rel="nofollow noreferrer">well</a></p>
<p>I got it working but I'm unsure why what I'm now doing is correct.</p>
<p>And the <code>openssl</code> feels unwieldy (advice appreciated).</p>
<h3>Environment</h3>
<pre class="lang-sh prettyprint-override"><code>DIR=${PWD}/secrets
SERVICE=&quot;...&quot;
NAMESPACE=&quot;...&quot;

FILENAME=&quot;${DIR}/${SERVICE}.${NAMESPACE}&quot;
</code></pre>
<h3>CA</h3>
<pre class="lang-sh prettyprint-override"><code>openssl req \
-nodes \
-new \
-x509 \
-keyout ${FILENAME}.ca.key \
-out ${FILENAME}.ca.crt \
-subj &quot;/CN=Validating Webhook CA&quot;
</code></pre>
<h3>Create (Webhook) Service</h3>
<p>Necessary to set the service certificate's CN to the IP</p>
<pre class="lang-sh prettyprint-override"><code>cat ./kubernetes/service.yaml \
| sed &quot;s|SERVICE|${SERVICE}|g&quot; \
| sed &quot;s|NAMESPACE|${NAMESPACE}|g&quot; \
| kubectl apply --filename=- --namespace=${NAMESPACE}

ENDPOINT=$(\
  kubectl get service/${SERVICE} \
  --namespace=${NAMESPACE} \
  --output=jsonpath=&quot;{.spec.clusterIP}&quot;) &amp;&amp; echo ${ENDPOINT}
</code></pre>
<h3>Create CSR</h3>
<p>Even though I include the CN and <code>alt_names</code> here, I must duplicate the SAN stuff (next step)</p>
<pre class="lang-sh prettyprint-override"><code>echo &quot;[ req ]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext

[ dn ]
commonName = ${ENDPOINT}

[ req_ext ]
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${SERVICE}.${NAMESPACE}.svc
DNS.2 = ${SERVICE}.${NAMESPACE}.svc.cluster.local
&quot; &gt; ${FILENAME}.cfg

openssl req \
-nodes \
-new \
-sha256 \
-newkey rsa:2048 \
-keyout ${FILENAME}.key \
-out ${FILENAME}.csr \
-config ${FILENAME}.cfg
</code></pre>
<h3>Create CSR extension</h3>
<p>Unsure why I must duplicate (or separate) this content. If I omit this from the <code>openssl x509 -extfile</code>, the certificate contains no SAN extension.</p>
<pre class="lang-sh prettyprint-override"><code>printf &quot;subjectAltName=DNS:${SERVICE}.${NAMESPACE}.svc,DNS:${SERVICE}.${NAMESPACE}.svc.cluster.local&quot; &gt; ${FILENAME}.ext
</code></pre>
<h3>Create service certificate</h3>
<p>How can I use a single CSR for everything rather than CSR+EXT?</p>
<pre class="lang-sh prettyprint-override"><code>openssl x509 \
-req \
-in ${FILENAME}.csr \
-extfile ${FILENAME}.ext \
-CA ${FILENAME}.ca.crt \
-CAkey ${FILENAME}.ca.key \
-CAcreateserial \
-out ${FILENAME}.crt
</code></pre>
<h3>Create (Webhook) Deployment</h3>
<p>The underlying implementation of the webhook needs the service's crt|key</p>
<pre class="lang-sh prettyprint-override"><code>kubectl create secret tls ${SERVICE} \
--namespace=${NAMESPACE} \
--cert=${FILENAME}.crt \
--key=${FILENAME}.key

cat ./kubernetes/deployment.yaml \
| sed &quot;s|SERVICE|${SERVICE}|g&quot; \
| sed &quot;s|NAMESPACE|${NAMESPACE}|g&quot; \
| kubectl apply --filename=- --namespace=${NAMESPACE}
</code></pre>
<h3>Create Webhook</h3>
<p>Grab the CA certificate</p>
<pre class="lang-sh prettyprint-override"><code>CABUNDLE=$(openssl base64 -A &lt;&quot;${FILENAME}.ca.crt&quot;)

cat ./kubernetes/webhook.yaml \
| sed &quot;s|SERVICE|${SERVICE}|g&quot; \
| sed &quot;s|NAMESPACE|${NAMESPACE}|g&quot; \
| sed &quot;s|CABUNDLE|${CABUNDLE}|g&quot; \
| kubectl apply --filename=- --namespace=${NAMESPACE}
</code></pre>
