<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jeongwoo Kim</title>
    <description>The latest articles on Forem by Jeongwoo Kim (@mlajkim).</description>
    <link>https://forem.com/mlajkim</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3633947%2F03481547-1a2b-491d-a03c-8755b4cd7ba8.jpg</url>
      <title>Forem: Jeongwoo Kim</title>
      <link>https://forem.com/mlajkim</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/mlajkim"/>
    <language>en</language>
    <item>
      <title>[Hands-on] Kubernetes Pod Certificate Request introduced in v1.35</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Tue, 07 Apr 2026 00:04:40 +0000</pubDate>
      <link>https://forem.com/mlajkim/hands-on-kubernetes-pod-certificate-request-introduced-in-v135-2ldb</link>
      <guid>https://forem.com/mlajkim/hands-on-kubernetes-pod-certificate-request-introduced-in-v135-2ldb</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
In hurry? Jump to the result!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal of this document is to generate auto signed certificate for any pod with the following projected volumes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;creds&lt;/span&gt;
  &lt;span class="na"&gt;projected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;podCertificate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;signerName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;row-major.net/spiffe&lt;/span&gt;
        &lt;span class="na"&gt;keyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ED25519&lt;/span&gt;
        &lt;span class="na"&gt;credentialBundlePath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service.crt&lt;/span&gt;
        &lt;span class="na"&gt;keyPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service.key&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;clusterTrustBundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;row-major.net:spiffe:primary-bundle&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ca.crt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Table of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;cover_image: ./thumbnail.png&lt;/li&gt;
&lt;li&gt;Goal&lt;/li&gt;
&lt;li&gt;Table of Contents&lt;/li&gt;
&lt;li&gt;
Walkthrough

&lt;ul&gt;
&lt;li&gt;Setup: Working Directory&lt;/li&gt;
&lt;li&gt;Setup: Kind Cluster with Cert Provisioning Enabled&lt;/li&gt;
&lt;li&gt;Setup: Mash Controller Deployed&lt;/li&gt;
&lt;li&gt;Verify: Auto Distributed Certificate Feature on Sample Deployment&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;What's next?&lt;/li&gt;

&lt;li&gt;Closing&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Walkthrough
&lt;/h1&gt;

&lt;p&gt;Here is the step-by-step record of how I achieved the goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Working Directory
&lt;/h2&gt;

&lt;p&gt;Let's quickly create a test directory build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;test_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pod_certificate_request
&lt;span class="nv"&gt;tmp_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%y%m%d_%H%M%S_&lt;span class="nv"&gt;$test_name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/test_dive/&lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/test_dive/&lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Kind Cluster with Cert Provisioning Enabled
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
Please note that the point of this blog's release already contains v1.35+ that includes the feature, so we do not need to set specific version.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Create a kind cluster locally with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_cluster_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"cert-provisioning"&lt;/span&gt;
&lt;span class="nv"&gt;_k8s_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"v1.35.0"&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; | kind create cluster --name "&lt;/span&gt;&lt;span class="nv"&gt;$_cluster_name&lt;/span&gt;&lt;span class="sh"&gt;" --image kindest/node:&lt;/span&gt;&lt;span class="nv"&gt;$_k8s_version&lt;/span&gt;&lt;span class="sh"&gt; --config -
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
featureGates:
  PodCertificateRequest: true
  ClusterTrustBundle: true
  ClusterTrustBundleProjection: true
runtimeConfig:
  "certificates.k8s.io/v1beta1/podcertificaterequests": "true"
  "certificates.k8s.io/v1beta1/clustertrustbundles": "true"
nodes:
- role: control-plane
- role: worker
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check new cluster created with version &lt;code&gt;v1.35.0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get nodes

&lt;span class="c"&gt;# NAME                              STATUS   ROLES           AGE   VERSION&lt;/span&gt;
&lt;span class="c"&gt;# cert-provisioning-control-plane   Ready    control-plane   29s   v1.35.0&lt;/span&gt;
&lt;span class="c"&gt;# cert-provisioning-worker          Ready    &amp;lt;none&amp;gt;          19s   v1.35.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Mash Controller Deployed
&lt;/h2&gt;

&lt;p&gt;Let's deploy the mesh-controller developed by @ahmedtd as a sample. First, clone the helper project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/ahmedtd/mesh-example.git mesh_example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we deploy the &lt;code&gt;mesh-controller&lt;/code&gt;, we need to create a CA pool secret that the controller can use to sign the certificate requests. The &lt;code&gt;mesh-controller&lt;/code&gt; expects two different CA pool secrets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;service-dns-ca-pool&lt;/code&gt;: for service DNS certificates between kubeapi server and mesh controller&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spiffe-ca-pool&lt;/code&gt;: for ca certificate to be used to sign the auto distributed certificates to pods&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The helper project provides the following command to create the CA pool &amp;amp; save as k8 secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;mesh_example &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; go run ./cmd/meshtool make-ca-pool-secret &lt;span class="nt"&gt;--namespace&lt;/span&gt; mesh-controller &lt;span class="nt"&gt;--name&lt;/span&gt; service-dns-ca-pool &lt;span class="nt"&gt;--ca-id&lt;/span&gt; 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;mesh_example &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; go run ./cmd/meshtool make-ca-pool-secret &lt;span class="nt"&gt;--namespace&lt;/span&gt; mesh-controller &lt;span class="nt"&gt;--name&lt;/span&gt; spiffe-ca-pool &lt;span class="nt"&gt;--ca-id&lt;/span&gt; 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check secrets created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get secrets &lt;span class="nt"&gt;-n&lt;/span&gt; mesh-controller

&lt;span class="c"&gt;# NAME                  TYPE     DATA   AGE&lt;/span&gt;
&lt;span class="c"&gt;# service-dns-ca-pool   Opaque   1      9s&lt;/span&gt;
&lt;span class="c"&gt;# spiffe-ca-pool        Opaque   1      9s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With secrets available as prerequisite, we can deploy the mesh controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; ./mesh_example/controller-manifests

&lt;span class="c"&gt;# namespace/mesh-controller configured&lt;/span&gt;
&lt;span class="c"&gt;# clusterrole.rbac.authorization.k8s.io/meshtool-signer created&lt;/span&gt;
&lt;span class="c"&gt;# clusterrolebinding.rbac.authorization.k8s.io/meshtool-is-a-meshtool-signer created&lt;/span&gt;
&lt;span class="c"&gt;# role.rbac.authorization.k8s.io/meshtool-controller created&lt;/span&gt;
&lt;span class="c"&gt;# rolebinding.rbac.authorization.k8s.io/meshtool-is-a-meshtool-controller created&lt;/span&gt;
&lt;span class="c"&gt;# deployment.apps/mesh-controller created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command above will create a namespace &lt;code&gt;mesh-controller&lt;/code&gt; and deploy the &lt;code&gt;mesh-controller&lt;/code&gt; in it. &lt;code&gt;mesh-controller&lt;/code&gt; is a custom controller and its current job is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watch &lt;code&gt;PodCertificateRequest&lt;/code&gt; resources, created by Kubernetes's kubelet with user's deployment's request&lt;/li&gt;
&lt;li&gt;Sign the certificate request with desiginated CA pool injected as a secret&lt;/li&gt;
&lt;li&gt;Return the signed certificate and key back to the kubelet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check if the mesh controller is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; mesh-controller

&lt;span class="c"&gt;# NAME                               READY   STATUS    RESTARTS   AGE&lt;/span&gt;
&lt;span class="c"&gt;# mesh-controller-547cd7996b-blqsf   1/1     Running   0          20s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verify: Auto Distributed Certificate Feature on Sample Deployment
&lt;/h2&gt;

&lt;p&gt;With all the prerequisites and the mesh controller in place, it's time to put it to the test. Let's deploy a sample application and verify if the certificates are automatically distributed and correctly mounted inside the pod.&lt;/p&gt;

&lt;p&gt;First of all, let's create a namespace for our sample application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace podcert-demo

&lt;span class="c"&gt;# namespace/podcert-demo created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, deploy a k8s deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: spiffe-demo-deploy
  namespace: podcert-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: spiffe-demo
  template:
    metadata:
      labels:
        app: spiffe-demo
    spec:
      serviceAccountName: default
      automountServiceAccountToken: false
      containers:
      - name: app
        image: alpine/openssl
        command: ["sh", "-lc", "sleep infinity"]
        volumeMounts:
        - name: creds
          mountPath: /var/run/tls
          readOnly: true
      volumes:
      - name: creds
        projected:
          sources:
          - podCertificate:
              signerName: row-major.net/spiffe
              keyType: ED25519
              credentialBundlePath: service.crt
              keyPath: service.key
          - clusterTrustBundle:
              name: row-major.net:spiffe:primary-bundle
              path: ca.crt
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# deployment.apps/spiffe-demo-deploy created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the kubelet can notice that this deployment (or its pods) require the certificate by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;creds&lt;/span&gt;
  &lt;span class="na"&gt;projected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;podCertificate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;signerName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;row-major.net/spiffe&lt;/span&gt;
        &lt;span class="na"&gt;keyType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ED25519&lt;/span&gt;
        &lt;span class="na"&gt;credentialBundlePath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service.crt&lt;/span&gt;
        &lt;span class="na"&gt;keyPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service.key&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;clusterTrustBundle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;row-major.net:spiffe:primary-bundle&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ca.crt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;mesh-controller&lt;/code&gt; by default is watching the signerName &lt;code&gt;row-major.net/spiffe&lt;/code&gt; and &lt;code&gt;row-major.net/service-dns&lt;/code&gt;. If we ever build our own signer, we can simply change the singerName in the deployment manifest.&lt;/p&gt;

&lt;p&gt;Finally, let's check the certificate and key are mounted on &lt;code&gt;/var/run/tls&lt;/code&gt; inside the pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; deploy/spiffe-demo-deploy &lt;span class="nt"&gt;-n&lt;/span&gt; podcert-demo &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-al&lt;/span&gt; /var/run/tls

&lt;span class="c"&gt;# total 4&lt;/span&gt;
&lt;span class="c"&gt;# drwxrwxrwt    3 root     root           140 Apr  6 02:46 .&lt;/span&gt;
&lt;span class="c"&gt;# drwxr-xr-x    1 root     root          4096 Apr  6 02:46 ..&lt;/span&gt;
&lt;span class="c"&gt;# drwxr-xr-x    2 root     root           100 Apr  6 02:46 ..2026_04_06_02_46_52.3629103651&lt;/span&gt;
&lt;span class="c"&gt;# lrwxrwxrwx    1 root     root            32 Apr  6 02:46 ..data -&amp;gt; ..2026_04_06_02_46_52.3629103651&lt;/span&gt;
&lt;span class="c"&gt;# lrwxrwxrwx    1 root     root            13 Apr  6 02:46 ca.crt -&amp;gt; ..data/ca.crt&lt;/span&gt;
&lt;span class="c"&gt;# lrwxrwxrwx    1 root     root            18 Apr  6 02:46 service.crt -&amp;gt; ..data/service.crt&lt;/span&gt;
&lt;span class="c"&gt;# lrwxrwxrwx    1 root     root            18 Apr  6 02:46 service.key -&amp;gt; ..data/service.key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the certificate auto distributed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; deploy/spiffe-demo-deploy &lt;span class="nt"&gt;-n&lt;/span&gt; podcert-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; openssl x509 &lt;span class="nt"&gt;-in&lt;/span&gt; /var/run/tls/service.crt &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-text&lt;/span&gt;

&lt;span class="c"&gt;# Certificate:&lt;/span&gt;
&lt;span class="c"&gt;#     Data:&lt;/span&gt;
&lt;span class="c"&gt;#         Version: 3 (0x2)&lt;/span&gt;
&lt;span class="c"&gt;#         Serial Number:&lt;/span&gt;
&lt;span class="c"&gt;#             02:20:72:93:62:05:37:22:fe:41:4f:ad:3f:ce:c5:bd:54:31:9e:0c&lt;/span&gt;
&lt;span class="c"&gt;#         Signature Algorithm: ED25519&lt;/span&gt;
&lt;span class="c"&gt;#         Issuer:&lt;/span&gt;
&lt;span class="c"&gt;#         Validity&lt;/span&gt;
&lt;span class="c"&gt;#             Not Before: Apr  6 02:44:52 2026 GMT&lt;/span&gt;
&lt;span class="c"&gt;#             Not After : Apr  7 02:44:52 2026 GMT&lt;/span&gt;
&lt;span class="c"&gt;#         Subject:&lt;/span&gt;
&lt;span class="c"&gt;#         Subject Public Key Info:&lt;/span&gt;
&lt;span class="c"&gt;#             Public Key Algorithm: ED25519&lt;/span&gt;
&lt;span class="c"&gt;#                 ED25519 Public-Key:&lt;/span&gt;
&lt;span class="c"&gt;#                 pub:&lt;/span&gt;
&lt;span class="c"&gt;#                     0e:bb:f2:f7:fc:ee:5f:21:83:c3:87:da:3c:eb:79:&lt;/span&gt;
&lt;span class="c"&gt;#                     dd:fe:5c:41:7e:90:cb:aa:e0:96:a8:64:e0:c5:1c:&lt;/span&gt;
&lt;span class="c"&gt;#                     11:90&lt;/span&gt;
&lt;span class="c"&gt;#         X509v3 extensions:&lt;/span&gt;
&lt;span class="c"&gt;#             X509v3 Key Usage: critical&lt;/span&gt;
&lt;span class="c"&gt;#                 Digital Signature&lt;/span&gt;
&lt;span class="c"&gt;#             X509v3 Extended Key Usage:&lt;/span&gt;
&lt;span class="c"&gt;#                 TLS Web Client Authentication, TLS Web Server Authentication&lt;/span&gt;
&lt;span class="c"&gt;#             X509v3 Basic Constraints: critical&lt;/span&gt;
&lt;span class="c"&gt;#                 CA:FALSE&lt;/span&gt;
&lt;span class="c"&gt;#             X509v3 Subject Alternative Name: critical&lt;/span&gt;
&lt;span class="c"&gt;#                 URI:spiffe://cluster.local/ns/podcert-demo/sa/default&lt;/span&gt;
&lt;span class="c"&gt;#     Signature Algorithm: ED25519&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see the root CA with one year validity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; deploy/spiffe-demo-deploy &lt;span class="nt"&gt;-n&lt;/span&gt; podcert-demo &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; openssl x509 &lt;span class="nt"&gt;-in&lt;/span&gt; /var/run/tls/ca.crt &lt;span class="nt"&gt;-noout&lt;/span&gt; &lt;span class="nt"&gt;-text&lt;/span&gt;

&lt;span class="c"&gt;# Certificate:&lt;/span&gt;
&lt;span class="c"&gt;#     Data:&lt;/span&gt;
&lt;span class="c"&gt;#         Version: 3 (0x2)&lt;/span&gt;
&lt;span class="c"&gt;#         Serial Number:&lt;/span&gt;
&lt;span class="c"&gt;#             70:f5:36:4e:2b:6f:9e:fc:fb:2d:cf:5d:32:77:65:a5:fe:5e:08:64&lt;/span&gt;
&lt;span class="c"&gt;#         Signature Algorithm: ED25519&lt;/span&gt;
&lt;span class="c"&gt;#         Issuer:&lt;/span&gt;
&lt;span class="c"&gt;#         Validit&lt;/span&gt;
&lt;span class="c"&gt;#             Not Before: Apr  6 02:39:27 2026 GMT&lt;/span&gt;
&lt;span class="c"&gt;#             Not After : Apr  6 02:39:27 2027 GMT&lt;/span&gt;
&lt;span class="c"&gt;#         Subject:&lt;/span&gt;
&lt;span class="c"&gt;#         Subject Public Key Info:&lt;/span&gt;
&lt;span class="c"&gt;#             Public Key Algorithm: ED25519&lt;/span&gt;
&lt;span class="c"&gt;#                 ED25519 Public-Key:&lt;/span&gt;
&lt;span class="c"&gt;#                 pub:&lt;/span&gt;
&lt;span class="c"&gt;#                     87:6d:54:67:8f:0c:d7:ed:5a:e4:a5:fd:a0:fb:81:&lt;/span&gt;
&lt;span class="c"&gt;#                     7d:e2:7e:84:44:dc:5f:2f:99:12:63:b4:08:2c:b0:&lt;/span&gt;
&lt;span class="c"&gt;#                     0b:5b&lt;/span&gt;
&lt;span class="c"&gt;#         X509v3 extensions:&lt;/span&gt;
&lt;span class="c"&gt;#             X509v3 Key Usage: critical&lt;/span&gt;
&lt;span class="c"&gt;#                 Digital Signature, Certificate Sign&lt;/span&gt;
&lt;span class="c"&gt;#             X509v3 Basic Constraints: critical&lt;/span&gt;
&lt;span class="c"&gt;#                 CA:TRUE&lt;/span&gt;
&lt;span class="c"&gt;#             X509v3 Subject Key Identifier:&lt;/span&gt;
&lt;span class="c"&gt;#                 A0:96:CE:C1:73:8E:63:5A:26:C2:0B:BE:45:46:4D:25:50:C4:7E:EB&lt;/span&gt;
&lt;span class="c"&gt;#     Signature Algorithm: ED25519&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is it! The kubelet automatically created a PodCertificateRequest and the mesh-controller signed it and returned the signed certificate and key back to the kubelet. The kubelet then mounted the certificate and key as a projected volume in the pod.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's next?
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;mesh-controller&lt;/code&gt; sample is a really good starting point to understand how the PodCertificateRequest works. However, if we want to generate a certificate signed by external CA, how can we do this? The next step is to create a controller that does detect the &lt;code&gt;PodCertificateRequest&lt;/code&gt;, and instead of signing by itself with secret injected CA, it forwards the request to the external authentication/authorization service (like Athenz), and returns the signed certificate and key back to the kubelet.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing
&lt;/h1&gt;

&lt;p&gt;If you enjoyed this deep dive, please leave a like &amp;amp; subscribe for more!&lt;/p&gt;

&lt;p&gt;Also, leave comments if you have any questions or suggestions. Thank you in advance!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo8bhe8rmqlg55oyvglf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo8bhe8rmqlg55oyvglf.png" alt="like_this_photo_cat" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>k8s</category>
      <category>podcertificaterequests</category>
      <category>athenz</category>
    </item>
    <item>
      <title>[Hands-on] Testing Separate Ports in Athenz ZTS</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Wed, 01 Apr 2026 00:13:15 +0000</pubDate>
      <link>https://forem.com/mlajkim/hands-on-testing-separate-ports-in-athenz-zts-2oac</link>
      <guid>https://forem.com/mlajkim/hands-on-testing-separate-ports-in-athenz-zts-2oac</guid>
      <description>&lt;h1&gt;
  
  
  [Hands-on] Testing Separate Ports in Athenz ZTS
&lt;/h1&gt;

&lt;p&gt;The goal of this test is to verify the new port-URI filtering feature introduced in &lt;a href="https://github.com/AthenZ/athenz/pull/3190" rel="noopener noreferrer"&gt;PR: Adding support to filter requests based on port-uri combination #3190&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Result
&lt;/h1&gt;

&lt;p&gt;I successfully reproduced the multiple ports configuration on the ZTS server. Initially, the server was only listening on the default port &lt;code&gt;4443&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Defaulted container "athenz-zts-server" out of: athenz-zts-server, zms-cli (init), athenz-conf (init), athenz-plugins (init)&lt;/span&gt;
&lt;span class="c"&gt;# sh: 1: ss: not found&lt;/span&gt;
&lt;span class="c"&gt;# Active Internet connections (only servers)&lt;/span&gt;
&lt;span class="c"&gt;# Proto Recv-Q Send-Q Local Address           Foreign Address         State&lt;/span&gt;
&lt;span class="c"&gt;# tcp6       0      0 :::4443                 :::*                    LISTEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying the new configuration, it now listens on both the original port &lt;code&gt;4443&lt;/code&gt; AND the newly added port &lt;code&gt;8443&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Active Internet connections (only servers)&lt;/span&gt;
&lt;span class="c"&gt;# Proto Recv-Q Send-Q Local Address           Foreign Address         State&lt;/span&gt;
&lt;span class="c"&gt;# tcp6       0      0 :::8443                 :::*                    LISTEN&lt;/span&gt;
&lt;span class="c"&gt;# tcp6       0      0 :::4443                 :::*                    LISTEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Table of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;[Hands-on] Testing Separate Ports in Athenz ZTS&lt;/li&gt;
&lt;li&gt;Result&lt;/li&gt;
&lt;li&gt;Table of Contents&lt;/li&gt;
&lt;li&gt;
Steps to Reproduce

&lt;ul&gt;
&lt;li&gt;Setup: Working Directory &amp;amp; Athenz Server&lt;/li&gt;
&lt;li&gt;Setup: port-uri configuration&lt;/li&gt;
&lt;li&gt;Setup: Volume Injection&lt;/li&gt;
&lt;li&gt;Setup: port open in ZTS&lt;/li&gt;
&lt;li&gt;Setup: Readiness/Liveness watching the port 8443&lt;/li&gt;
&lt;li&gt;Setup: athenz.properties&lt;/li&gt;
&lt;li&gt;Setup: Rollback to 4443&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;What I learned&lt;/li&gt;

&lt;li&gt;What's next?&lt;/li&gt;

&lt;li&gt;Closing&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Steps to Reproduce
&lt;/h1&gt;

&lt;p&gt;Walk through the following steps to achieve the same result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Working Directory &amp;amp; Athenz Server
&lt;/h2&gt;

&lt;p&gt;Let's quickly create a test directory build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;test_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;separate_port_in_athenz
&lt;span class="nv"&gt;tmp_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%y%m%d_%H%M%S_&lt;span class="nv"&gt;$test_name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/test_dive/&lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/test_dive/&lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tutorial has a one-command setup script that will create a local kubernetes cluster with kind, and deploy athenz server into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mlajkim/dive-manifest.git manifest
make &lt;span class="nt"&gt;-C&lt;/span&gt; manifest setup

&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="c"&gt;# ✅ Athenz Server deployment finished&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that every pod is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; athenz

&lt;span class="c"&gt;# NAME                                 READY   STATUS    RESTARTS   AGE&lt;/span&gt;
&lt;span class="c"&gt;# athenz-cli-574d747dff-qk8lr          1/1     Running   0          5m5s&lt;/span&gt;
&lt;span class="c"&gt;# athenz-db-0                          1/1     Running   0          5m5s&lt;/span&gt;
&lt;span class="c"&gt;# athenz-ui-59f7f77667-wgr5l           2/2     Running   0          5m4s&lt;/span&gt;
&lt;span class="c"&gt;# athenz-zms-server-568d4cfd89-whcjn   1/1     Running   0          5m4s&lt;/span&gt;
&lt;span class="c"&gt;# athenz-zts-server-6966ff7f66-897lg   1/1     Running   0          5m4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: port-uri configuration
&lt;/h2&gt;

&lt;p&gt;Please take a look what is inside the &lt;a href="https://raw.githubusercontent.com/AthenZ/athenz/refs/heads/master/containers/jetty/conf/port-uri.json.example" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com/AthenZ/athenz/refs/heads/master/containers/jetty/conf/port-uri.json.example&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This sample configuration file is basically saying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This file is a security rulebook that decides which API paths can be accessed and whether strict security (mTLS) is required for four different server ports.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port 4443&lt;/strong&gt; is the main port that allows all API requests, but it absolutely requires mTLS to keep things safe. &lt;/li&gt;
&lt;li&gt;The other three ports don't need mTLS, but they are strictly locked down to only handle their own specific jobs—like registration (9443), health checks (8443), or OpenID (443).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use the following commands to create a K8s cm from the sample file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz
&lt;span class="nv"&gt;_cm_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;port-uri-config
&lt;span class="nv"&gt;_cm_file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;port-uri.json

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$_cm_file_name&lt;/span&gt;&lt;span class="sh"&gt;
{
  "ports": [
    {
      "port": 4443,
      "mtls_required": false,
      "description": "Main API port - all endpoints with default 4443, but no mTLS required for test sake",
      "allowed_endpoints": []
    },
    {
      "port": 8443,
      "mtls_required": false,
      "description": "Health/status port - /zts/v1/status (ZTS API) and /status (file-based health check returning OK)",
      "allowed_endpoints": [
        {
          "path": "/zts/v1/status",
          "methods": ["GET"],
          "description": "ZTS API status - returns JSON { &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;code&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;: 200, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;message&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;OK&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt; }"
        },
        {
          "path": "/status",
          "methods": ["GET"],
          "description": "Legacy file-based health check - returns OK when athenz.health_check_uri_list includes /status"
        }
      ]
    }
  ]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;kubectl create ns &lt;span class="nv"&gt;$_ns&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
kubectl create configmap &lt;span class="nv"&gt;$_cm_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$_cm_file_name&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; - &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nv"&gt;$_cm_file_name&lt;/span&gt;

&lt;span class="c"&gt;# configmap/port-uri-config created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get cm &lt;span class="nv"&gt;$_cm_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt;

&lt;span class="c"&gt;# NAME               DATA   AGE&lt;/span&gt;
&lt;span class="c"&gt;# port-uri-config    1      18s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Volume Injection
&lt;/h2&gt;

&lt;p&gt;Let's mount the cm created above into the zts server pod.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz
&lt;span class="nv"&gt;_cm_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;port-uri-config
&lt;span class="nv"&gt;_cm_file_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;port-uri.json
&lt;span class="nv"&gt;_deploy_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_mnt_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/opt/athenz/zts/conf/&lt;span class="nv"&gt;$_cm_file_name&lt;/span&gt;
&lt;span class="nv"&gt;_patch_json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[
  {
    "op": "add",
    "path": "/spec/template/spec/volumes/-",
    "value": {
      "name": "port-uri-vol",
      "configMap": {
        "name": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_cm_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
      }
    }
  },
  {
    "op": "add",
    "path": "/spec/template/spec/containers/0/volumeMounts/-",
    "value": {
      "name": "port-uri-vol",
      "mountPath": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_mnt_path&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;",
      "subPath": "&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_cm_file_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"
    }
  }
]
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

kubectl patch deployment &lt;span class="nv"&gt;$_deploy_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_patch_json&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# deployment.apps/athenz-zts-server patched&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_deploy_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; deployment/&lt;span class="nv"&gt;$_deploy_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'ls -l /opt/athenz/zts/conf/port-uri.json &amp;amp;&amp;amp; cat /opt/athenz/zts/conf/port-uri.json'&lt;/span&gt;

&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="c"&gt;#         {&lt;/span&gt;
&lt;span class="c"&gt;#           "path_starts_with": "/zts/v1/.well-known",&lt;/span&gt;
&lt;span class="c"&gt;#           "path_ends_with": "openid-configuration",&lt;/span&gt;
&lt;span class="c"&gt;#           "methods": ["GET"],&lt;/span&gt;
&lt;span class="c"&gt;#           "description": "OpenID discovery (alternative using path_starts_with and path_ends_with)"&lt;/span&gt;
&lt;span class="c"&gt;#         }&lt;/span&gt;
&lt;span class="c"&gt;#       ]&lt;/span&gt;
&lt;span class="c"&gt;#     }&lt;/span&gt;
&lt;span class="c"&gt;#   ]&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: port open in ZTS
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIPS]&lt;br&gt;
You can learn what kind of port is required with the &lt;a href="https://raw.githubusercontent.com/AthenZ/athenz/refs/heads/master/containers/jetty/conf/port-uri.json.example" rel="noopener noreferrer"&gt;port-uri.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[!NOTE]&lt;br&gt;
We only need the &lt;code&gt;status&lt;/code&gt;, but for reference sake, let's set up multiple ports open (not security best practice, but this is tutorial!)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The default port &lt;code&gt;https:4443&lt;/code&gt; is used for all API requests by manifest default. Let's open other ports as well in Kubernetes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_deploy_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl patch deployment &lt;span class="nv"&gt;$_deploy_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'[
  { "op": "add", "path": "/spec/template/spec/containers/0/ports/-", "value": { "containerPort": 8443, "name": "status", "protocol": "TCP" } },
  { "op": "add", "path": "/spec/template/spec/containers/0/ports/-", "value": { "containerPort": 9443, "name": "instance", "protocol": "TCP" } },
  { "op": "add", "path": "/spec/template/spec/containers/0/ports/-", "value": { "containerPort": 443, "name": "openid", "protocol": "TCP" } }
]'&lt;/span&gt;

&lt;span class="c"&gt;# deployment.apps/athenz-zts-server patched&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_deploy_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl get deployment &lt;span class="nv"&gt;$_deploy_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; custom-columns&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"PORTS:.spec.template.spec.containers[0].ports[*].containerPort"&lt;/span&gt;

&lt;span class="c"&gt;# PORTS&lt;/span&gt;
&lt;span class="c"&gt;# 4443,8443,9443,443&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Readiness/Liveness watching the port 8443
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
This phase deliberately fails readiness state and will be fixed in the next phase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have done the following so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;k8s cm created and mounted as a file in ZTS pod&lt;/li&gt;
&lt;li&gt;ZTS deployment patched to open ports required to communicate between ZTS and requsters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we want to change the readiness probe to watch the port &lt;code&gt;8443&lt;/code&gt; instead of 4443:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz
&lt;span class="nv"&gt;_deploy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8443
&lt;span class="nv"&gt;_patch_json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/readinessProbe/exec/command",
    "value": [
      "curl",
      "-s",
      "--fail",
      "--resolve",
      "athenz-zts-server.athenz:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:127.0.0.1",
      "https://athenz-zts-server.athenz:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/zts/v1/status"
    ]
  },
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/livenessProbe/exec/command",
    "value": [
      "curl",
      "-s",
      "--fail",
      "--resolve",
      "athenz-zts-server.athenz:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:127.0.0.1",
      "https://athenz-zts-server.athenz:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/zts/v1/status"
    ]
  }
]
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

kubectl patch deployment &lt;span class="nv"&gt;$_deploy&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_patch_json&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# deployment.apps/athenz-zts-server patched&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's check the status of the deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt;


&lt;span class="c"&gt;# NAME                                 READY   STATUS    RESTARTS      AGE&lt;/span&gt;
&lt;span class="c"&gt;# athenz-cli-574d747dff-7466j          1/1     Running   0             14m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-db-0                          1/1     Running   0             14m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-ui-59f7f77667-99ws6           2/2     Running   0             14m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-zms-server-568d4cfd89-tk8td   1/1     Running   0             14m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-zts-server-68686cbb54-rhxzs   0/1     Running   1 (48s ago)   109s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As expected, the pod fails the readiness check and goes into a restart loop. This happens because the ZTS server doesn't know about our mounted &lt;code&gt;port-uri.json&lt;/code&gt; file yet, so it's still only listening on the default port &lt;code&gt;4443&lt;/code&gt;. We can verify this by checking the active ports inside the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_deploy_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; deployment/&lt;span class="nv"&gt;$_deploy_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'ss -ltn || netstat -tln'&lt;/span&gt;

&lt;span class="c"&gt;# Defaulted container "athenz-zts-server" out of: athenz-zts-server, zms-cli (init), athenz-conf (init), athenz-plugins (init)&lt;/span&gt;
&lt;span class="c"&gt;# sh: 1: ss: not found&lt;/span&gt;
&lt;span class="c"&gt;# Active Internet connections (only servers)&lt;/span&gt;
&lt;span class="c"&gt;# Proto Recv-Q Send-Q Local Address           Foreign Address         State&lt;/span&gt;
&lt;span class="c"&gt;# tcp6       0      0 :::4443                 :::*                    LISTEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: athenz.properties
&lt;/h2&gt;

&lt;p&gt;To fix the readiness problem, let's finally add a property &lt;code&gt;athenz.port_uri_config&lt;/code&gt; to &lt;code&gt;athenz.properties&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_cm_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-conf
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz
&lt;span class="nv"&gt;_prop_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"athenz.properties"&lt;/span&gt;
&lt;span class="nv"&gt;_new_line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"athenz.port_uri_config=/opt/athenz/zts/conf/port-uri.json"&lt;/span&gt;

kubectl get cm &lt;span class="nv"&gt;$_cm_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; json | &lt;span class="se"&gt;\&lt;/span&gt;
jq &lt;span class="nt"&gt;--arg&lt;/span&gt; file &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_prop_file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--arg&lt;/span&gt; line &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_new_line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s1"&gt;'.data[$file] += "\n" + $line + "\n"'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; -

&lt;span class="c"&gt;# configmap/athenz-zts-conf replaced&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In k8s, you need to restart the deployment to read the new cm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_deploy_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl rollout restart deployment/&lt;span class="nv"&gt;$_deploy_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt;

&lt;span class="c"&gt;# deployment.apps/athenz-zts-server restarted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see if listening ports have been changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_deploy_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; deployment/&lt;span class="nv"&gt;$_deploy_name&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'ss -ltn || netstat -tln'&lt;/span&gt;

&lt;span class="c"&gt;# Active Internet connections (only servers)&lt;/span&gt;
&lt;span class="c"&gt;# Proto Recv-Q Send-Q Local Address           Foreign Address         State&lt;/span&gt;
&lt;span class="c"&gt;# tcp6       0      0 :::8443                 :::*                    LISTEN&lt;/span&gt;
&lt;span class="c"&gt;# tcp6       0      0 :::4443                 :::*                    LISTEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see the pod status as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt;

&lt;span class="c"&gt;# NAME                                 READY   STATUS    RESTARTS   AGE&lt;/span&gt;
&lt;span class="c"&gt;# athenz-cli-574d747dff-7466j          1/1     Running   0          19m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-db-0                          1/1     Running   0          19m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-ui-59f7f77667-99ws6           2/2     Running   0          19m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-zms-server-568d4cfd89-tk8td   1/1     Running   0          19m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-zts-server-797757d5cb-dqzs8   1/1     Running   0          26s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Rollback to 4443
&lt;/h2&gt;

&lt;p&gt;What happens when we roll back to the &lt;code&gt;4443&lt;/code&gt; from &lt;code&gt;8443&lt;/code&gt; for readiness and liveness?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz
&lt;span class="nv"&gt;_deploy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz-zts-server
&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4443
&lt;span class="nv"&gt;_patch_json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/readinessProbe/exec/command",
    "value": [
      "curl",
      "-s",
      "--fail",
      "--resolve",
      "athenz-zts-server.athenz:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:127.0.0.1",
      "https://athenz-zts-server.athenz:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/zts/v1/status"
    ]
  },
  {
    "op": "replace",
    "path": "/spec/template/spec/containers/0/livenessProbe/exec/command",
    "value": [
      "curl",
      "-s",
      "--fail",
      "--resolve",
      "athenz-zts-server.athenz:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;:127.0.0.1",
      "https://athenz-zts-server.athenz:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_new_status_port&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;/zts/v1/status"
    ]
  }
]
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

kubectl patch deployment &lt;span class="nv"&gt;$_deploy&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$_patch_json&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# deployment.apps/athenz-zts-server patched&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see the pod status will be changed from Running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;5
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz

kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt;

&lt;span class="c"&gt;# NAME                                 READY   STATUS    RESTARTS   AGE&lt;/span&gt;
&lt;span class="c"&gt;# athenz-cli-574d747dff-7466j          1/1     Running   0          21m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-db-0                          1/1     Running   0          21m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-ui-59f7f77667-99ws6           2/2     Running   0          21m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-zms-server-568d4cfd89-tk8td   1/1     Running   0          21m&lt;/span&gt;
&lt;span class="c"&gt;# athenz-zts-server-78b94c8948-884qq   1/1     Running   0          35s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why is it still Running after rolling back to &lt;code&gt;4443&lt;/code&gt;? If you recall our &lt;code&gt;port-uri.json&lt;/code&gt; configuration, the &lt;code&gt;4443&lt;/code&gt; port has an empty &lt;code&gt;allowed_endpoints&lt;/code&gt; array &lt;code&gt;[]&lt;/code&gt;, meaning it accepts all paths, including &lt;code&gt;/status&lt;/code&gt;. This overlapping configuration enables a seamless, zero-downtime migration to the new port in production environments.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I learned
&lt;/h1&gt;

&lt;p&gt;I learned that I really enjoyed the following command as it was a bit hassle to do it manually for cluster setup &amp;amp; Athenz setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mlajkim/dive-manifest.git manifest
make &lt;span class="nt"&gt;-C&lt;/span&gt; manifest setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, sometimes I crash ZMS/ZTS servers, which is a bit hassle to delete again + redeploy again.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's next?
&lt;/h1&gt;

&lt;p&gt;I will keep writing hands-on tutorials for other PRs as well. Stay tuned!&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing
&lt;/h1&gt;

&lt;p&gt;If you enjoyed this deep dive, please leave a like &amp;amp; subscribe for more!&lt;/p&gt;

&lt;p&gt;Also, leave comments if you have any questions or suggestions. Thank you in advance!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo8bhe8rmqlg55oyvglf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foo8bhe8rmqlg55oyvglf.png" alt="like_this_photo_cat" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>athenz</category>
      <category>port</category>
    </item>
    <item>
      <title>Read Athenz Patch Note v1.12.27</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Fri, 06 Mar 2026 20:11:35 +0000</pubDate>
      <link>https://forem.com/mlajkim/read-athenz-patch-note-v11227-5d5h</link>
      <guid>https://forem.com/mlajkim/read-athenz-patch-note-v11227-5d5h</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
In hurry? Jump directly to Result section to see the outcome of this dive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal of this dive is to read and understand the patch notes for &lt;a href="https://github.com/AthenZ/athenz/releases/tag/v1.12.27" rel="noopener noreferrer"&gt;Athenz v1.12.27&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  ToC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Goal&lt;/li&gt;
&lt;li&gt;
ToC

&lt;ul&gt;
&lt;li&gt;PR: expose on-call URL value in client-side config#3055&lt;/li&gt;
&lt;li&gt;Glossary&lt;/li&gt;
&lt;li&gt;PR summary in my own words&lt;/li&gt;
&lt;li&gt;PR summary from AI&lt;/li&gt;
&lt;li&gt;
Can I test it out?

&lt;ul&gt;
&lt;li&gt;Setup: &lt;code&gt;extend-config.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Setup: &lt;code&gt;ON_CALL_URL&lt;/code&gt; env for the &lt;code&gt;athenz-ui&lt;/code&gt; deployment&lt;/li&gt;
&lt;li&gt;Verify: On Call URL&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PR: ui - switch from zms to msd for policy creation by @ArtjomsPorss in #3034&lt;/li&gt;

&lt;li&gt;Glossary&lt;/li&gt;

&lt;li&gt;PR summary in my own words&lt;/li&gt;

&lt;li&gt;PR summary from AI&lt;/li&gt;

&lt;li&gt;Can I test it out?&lt;/li&gt;

&lt;li&gt;PR: feat: Add functionality to search My Domains in UI by @chandrasekhar1996 in #3058&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Can I test it out?&lt;/li&gt;

&lt;li&gt;PR: fix: preserve domain contacts when updating an individual contact without page refresh by @chandrasekhar1996 in #3083&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;

Can I test it out?

&lt;ul&gt;
&lt;li&gt;Setup: ZMS properties &lt;code&gt;domain_contact_types&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Setup: UI users_data.json&lt;/li&gt;
&lt;li&gt;Setup: Patch UI deployment to read the users_data.json&lt;/li&gt;
&lt;li&gt;Verify: UI Behavior&lt;/li&gt;
&lt;li&gt;Verify: UI Network&lt;/li&gt;
&lt;li&gt;Verify: DB Table&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PR: Use correct URL path and query param for athenz role. #3089&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;Can I test it out?&lt;/li&gt;

&lt;li&gt;What I learned&lt;/li&gt;

&lt;li&gt;PR: use metadata to specify use of default identity #3084&lt;/li&gt;

&lt;li&gt;Prerequisites&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Can I test it out?: Yes, but skipped&lt;/li&gt;

&lt;li&gt;Future Potentials&lt;/li&gt;

&lt;li&gt;PR: Make ZpeUpdPolLoader ScheduledExecutorService thread daemon #3086&lt;/li&gt;

&lt;li&gt;

Prerequisites

&lt;ul&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;2&lt;/li&gt;
&lt;li&gt;3&lt;/li&gt;
&lt;li&gt;4&lt;/li&gt;
&lt;li&gt;5&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;

Can I test it out?: Yes

&lt;ul&gt;
&lt;li&gt;Setup: Namespace &lt;code&gt;athenz&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Setup: Java code as a CM&lt;/li&gt;
&lt;li&gt;Verify: v1.12.26 vs v1.12.27&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Future Potentials&lt;/li&gt;

&lt;li&gt;PR: make otel metric options more configurable #3090&lt;/li&gt;

&lt;li&gt;

Prerequisites

&lt;ul&gt;
&lt;li&gt;1&lt;/li&gt;
&lt;li&gt;2&lt;/li&gt;
&lt;li&gt;3&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Breaking Changes?: Yes&lt;/li&gt;

&lt;li&gt;Can I test it out?: Yes, but skipped&lt;/li&gt;

&lt;li&gt;Future Potentials&lt;/li&gt;

&lt;li&gt;PR: expose openid_issuer field for access tokens in zts java client #3091&lt;/li&gt;

&lt;li&gt;Prerequisites&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Breaking Changes?: No&lt;/li&gt;

&lt;li&gt;Can I test it out?: Yes, but skipped&lt;/li&gt;

&lt;li&gt;Future Potentials&lt;/li&gt;

&lt;li&gt;PR: Add FreeBSD support to libs/go/sia/util #3093&lt;/li&gt;

&lt;li&gt;Glossary&lt;/li&gt;

&lt;li&gt;Prerequisites&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Breaking Changes?: No&lt;/li&gt;

&lt;li&gt;Can I test it out?: Yes, but skipped&lt;/li&gt;

&lt;li&gt;Future Potentials&lt;/li&gt;

&lt;li&gt;PR: expose x509/ssh key id for instance register/refresh operations #3092&lt;/li&gt;

&lt;li&gt;Prerequisites&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Breaking Changes?: No&lt;/li&gt;

&lt;li&gt;Can I test it out?: Yes, but skipped&lt;/li&gt;

&lt;li&gt;Future Potentials&lt;/li&gt;

&lt;li&gt;PR: fix util test os filenames + new GetGroupGID impl #3094&lt;/li&gt;

&lt;li&gt;Prerequisites&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Breaking Changes?: No&lt;/li&gt;

&lt;li&gt;Can I test it out?: Yes, but skipped&lt;/li&gt;

&lt;li&gt;Future Potentials&lt;/li&gt;

&lt;li&gt;PR: update go and java dependencies to their latest releases #3095&lt;/li&gt;

&lt;li&gt;Prerequisites&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Breaking Changes?: No&lt;/li&gt;

&lt;li&gt;Can I test it out?: Yes, but skipped&lt;/li&gt;

&lt;li&gt;Future Potential&lt;/li&gt;

&lt;li&gt;PR: allow wildcard in first domain component of StaticWorkloadName #3096&lt;/li&gt;

&lt;li&gt;Glossary&lt;/li&gt;

&lt;li&gt;Prerequisites&lt;/li&gt;

&lt;li&gt;PR Summary in my own words&lt;/li&gt;

&lt;li&gt;PR Summary from AI&lt;/li&gt;

&lt;li&gt;Breaking Changes?: No&lt;/li&gt;

&lt;li&gt;Can I test it out?: No&lt;/li&gt;

&lt;li&gt;Future Potential&lt;/li&gt;

&lt;li&gt;What's Next?&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;Closing&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  PR: expose on-call URL value in client-side config#3055
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3055" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3055&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Glossary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;On call&lt;/code&gt;: Emergency calls&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;client-side&lt;/code&gt;: Web browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  PR summary in my own words
&lt;/h3&gt;

&lt;p&gt;Simply speaking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Before&lt;/code&gt;: We had to set the &lt;code&gt;NEXT_PUBLIC_ONCALL_URL&lt;/code&gt; environment variable at &lt;em&gt;build&lt;/em&gt; time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;After&lt;/code&gt;: We can now set &lt;code&gt;onCallUrl=""&lt;/code&gt; in the client-side configuration or as an ENV variable. There is no need to rebuild =&amp;gt; Simply modify the config and restart the pod.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just like we can set the &lt;code&gt;ZMS_URL&lt;/code&gt; for &lt;code&gt;k8s-athenz-sia&lt;/code&gt; without needing to rebuild the image.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request refactors how the on-call URL is exposed to the client-side in the UI application. It moves away from &lt;br&gt;
relying on NEXT_PUBLIC environment variables, which are baked in at build time, to a more robust client-side configuration mechanism using Next.js's publicRuntimeConfig. This ensures the onCallUrl is consistently available for client-side usage without being tied to the build process.&lt;/p&gt;
&lt;h3&gt;
  
  
  Can I test it out?
&lt;/h3&gt;

&lt;p&gt;Yes, I've done it.&lt;/p&gt;
&lt;h4&gt;
  
  
  Setup: &lt;code&gt;extend-config.js&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The following change allows the athenz-ui to read the ENV value &lt;code&gt;ON_CALL_URL&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;extended-config.js&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;'use strict';&lt;/span&gt;

    &lt;span class="s"&gt;const config = {&lt;/span&gt;
        &lt;span class="s"&gt;authProxy: {&lt;/span&gt;
            &lt;span class="s"&gt;onCallUrl: process.env.ON_CALL_URL, &amp;lt;&amp;lt; HERE&lt;/span&gt;
            &lt;span class="s"&gt;timeZone: 'Asia/Tokyo',&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Setup: &lt;code&gt;ON_CALL_URL&lt;/code&gt; env for the &lt;code&gt;athenz-ui&lt;/code&gt; deployment
&lt;/h4&gt;

&lt;p&gt;And pass the env &lt;code&gt;ON_CALL_URL&lt;/code&gt; inside the k8s deployment of the &lt;code&gt;athenz-ui&lt;/code&gt; pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NODE_EXTRA_CA_CERTS&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs/ca-certificates.crt&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ON_CALL_URL&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://www.google.com &amp;lt; HERE&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/mlajkim/athenz-ui:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify: On Call URL
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39ixqt575t1c8g4fv2hc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39ixqt575t1c8g4fv2hc.gif" alt="test_on_call_feature_on_ui" width="1678" height="770"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose any athenz domain in UI (&lt;a href="http://localhost:3000/domain/home/role" rel="noopener noreferrer"&gt;Sample URL&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;More Details&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;code&gt;add&lt;/code&gt; on the &lt;code&gt;On Call&lt;/code&gt; section (The name of the team will be displayed if you already have set up)&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;whatever team member&lt;/code&gt; you want to test as&lt;/li&gt;
&lt;li&gt;Click the link; You will be redirected to &lt;code&gt;{ON_CALL_URL}/{whatever team member}&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  PR: ui - switch from zms to msd for policy creation by @ArtjomsPorss in #3034
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3034" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3034&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Glossary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MSD&lt;/code&gt;: Or Micro Segmentation Daemon, is basically an API server that sets up IP policies in the ZMS Server to achieve micro-segmentation. Note that IPs change all the time in a cloud environment, so something has to handle these changes continuously.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  PR summary in my own words
&lt;/h3&gt;

&lt;p&gt;The UI used to handle Micro-Segmentation business logic by communicating directly with ZMS. Now, the MSD handles this for the UI instead. The UI simply calls the API, and it's done.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request refactors the microsegmentation policy management workflow within the UI. It transitions the backend interaction from orchestrating multiple calls to the ZMS to directly communicating with the Microsegmentation Daemon (MSD). By utilizing the &lt;code&gt;createOrUpdateTransportPolicy&lt;/code&gt; API, this change significantly simplifies client-side logic by removing redundant ZMS calls and delegates policy validation and IP mapping directly to the MSD service.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?
&lt;/h3&gt;

&lt;p&gt;No. &lt;code&gt;MSD&lt;/code&gt; is not yet open-sourced.&lt;br&gt;
So only Yahoo Inc. can test this. However, I learned that I should do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;featureFlag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;pageFeatureFlag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;microsegmentation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;policyValidation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables the MS feature on the UI. But since the PR is just a refactor, the behavior is expected to remain the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  PR: feat: Add functionality to search My Domains in UI by @chandrasekhar1996 in #3058
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3058" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3058&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;A Athenz-UI-side filter (search) feature for the domain list.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request introduces a new search capability for the 'My Domains' section in the UI. It allows users to efficiently filter their list of domains using a search bar, improving navigation and usability for large domain sets. The implementation includes client-side filtering with intelligent result prioritization and robust input handling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?
&lt;/h3&gt;

&lt;p&gt;Yes, you can directly search on UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqkx77s2808h54w884yt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqkx77s2808h54w884yt.gif" alt="search_in_domain_list" width="1592" height="916"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  PR: fix: preserve domain contacts when updating an individual contact without page refresh by @chandrasekhar1996 in #3083
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3083" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3083&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;It fixes a bug that required users to refresh the page to see updated &lt;code&gt;contact info&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request implements a crucial fix to prevent the loss of domain contact information when an individual contact (like a product owner or security owner) is updated without a full page refresh. By introducing a new helper method, the system now intelligently merges existing contacts with the newly updated ones, ensuring that all contact types are preserved during the update process. This enhances data integrity and provides a smoother user experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?
&lt;/h3&gt;

&lt;p&gt;I was not able to test it out right away and got the following status:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Was able to add the contact&lt;/li&gt;
&lt;li&gt;✅ Was able to see the contact in the DB&lt;/li&gt;
&lt;li&gt;❌ Failed to show the added contact in the UI&lt;/li&gt;
&lt;li&gt;❌ Failed to reproduce the issue&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Setup: ZMS properties &lt;code&gt;domain_contact_types&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The UI defaults to these two and sends these types based on that, so we need to let ZMS know about them too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zms.properties: |
    athenz.zms.domain_contact_types&lt;span class="o"&gt;=&lt;/span&gt;product-owner,security-owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will fix the following error:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0vlki3bswpqs26sm1qi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj0vlki3bswpqs26sm1qi.png" alt="invalid_domain_contact_type_product_owner" width="743" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Setup: UI users_data.json
&lt;/h4&gt;

&lt;p&gt;Add hard-coded users to the UI for testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;cat &amp;lt;&amp;lt;EOF | kubectl apply -f -&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;athenz-ui-users-cm&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;athenz&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;users_data.json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;[&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"is_human": 1,&lt;/span&gt;
        &lt;span class="s"&gt;"login": "__admin__",&lt;/span&gt;
        &lt;span class="s"&gt;"gecos": "__fullname__",&lt;/span&gt;
        &lt;span class="s"&gt;"enabled_status": 1&lt;/span&gt;
      &lt;span class="s"&gt;},&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"is_human": 1,&lt;/span&gt;
        &lt;span class="s"&gt;"login": "jekim",&lt;/span&gt;
        &lt;span class="s"&gt;"gecos": "Jeongwoo Kim",&lt;/span&gt;
        &lt;span class="s"&gt;"enabled_status": 1&lt;/span&gt;
      &lt;span class="s"&gt;},&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"is_human": 1,&lt;/span&gt;
        &lt;span class="s"&gt;"login": "athenz_admin",&lt;/span&gt;
        &lt;span class="s"&gt;"gecos": "Athenz Admin",&lt;/span&gt;
        &lt;span class="s"&gt;"enabled_status": 1&lt;/span&gt;
      &lt;span class="s"&gt;},&lt;/span&gt;
      &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"is_human": 1,&lt;/span&gt;
        &lt;span class="s"&gt;"login": "test",&lt;/span&gt;
        &lt;span class="s"&gt;"gecos": "Test User",&lt;/span&gt;
        &lt;span class="s"&gt;"enabled_status": 1&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;]&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Setup: Patch UI deployment to read the users_data.json
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl patch deployment athenz-ui &lt;span class="nt"&gt;-n&lt;/span&gt; athenz &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'json'&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'[
  {
    "op": "add",
    "path": "/spec/template/spec/volumes/-",
    "value": {
      "name": "users-config",
      "configMap": {
        "name": "athenz-ui-users-cm"
      }
    }
  },
  {
    "op": "add",
    "path": "/spec/template/spec/containers/0/volumeMounts/-",
    "value": {
      "name": "users-config",
      "mountPath": "/home/athenz/src/config/users_data.json",
      "subPath": "users_data.json"
    }
  }
]'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify: UI Behavior
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
I was not able to reproduce the issue yet, but I was able to add the contact.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzcieke8lruadvxla0uu.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzcieke8lruadvxla0uu.gif" alt="point_of_conduct_setup" width="1592" height="916"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Verify: UI Network
&lt;/h4&gt;

&lt;p&gt;Confirm that an API call was made to &lt;code&gt;/api/v1/domain;domain=user?returnMeta=true&lt;/code&gt; containing the &lt;code&gt;contacts&lt;/code&gt; field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"auditEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ypmId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contacts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"product-owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.athenz_admin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"security-owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user.jekim"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"autoDeleteTenantAssumeRoleAssertions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"modified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-09T23:15:19.456Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"75911440-052f-11f1-b5de-8cd6cbf5b517"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify: DB Table
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mariadb

SHOW DATABASES&lt;span class="p"&gt;;&lt;/span&gt;
USE zms_server&lt;span class="p"&gt;;&lt;/span&gt;

SHOW TABLES&lt;span class="p"&gt;;&lt;/span&gt;
SELECT &lt;span class="k"&gt;*&lt;/span&gt; FROM domain_contacts
WHERE domain_id &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;
    SELECT domain_id
    FROM domain
    WHERE name &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'user'&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# +-----------+----------------+-------------------+&lt;/span&gt;
&lt;span class="c"&gt;# | domain_id | type           | name              |&lt;/span&gt;
&lt;span class="c"&gt;# +-----------+----------------+-------------------+&lt;/span&gt;
&lt;span class="c"&gt;# |         1 | product-owner  | user.athenz_admin |&lt;/span&gt;
&lt;span class="c"&gt;# |         1 | security-owner | user.jekim        |&lt;/span&gt;
&lt;span class="c"&gt;# +-----------+----------------+-------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  PR: Use correct URL path and query param for athenz role. #3089
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3089" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3089&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;Fixes the documentation incorrectness&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;Uses Athenz user role members as a SSOT to control who has access to specific Azure resources,&lt;br&gt;
without relying on Azure Console's user management. It simply fixes an inaccuracy in the documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;// principal authorized to request a given scope &lt;span class="k"&gt;in &lt;/span&gt;the credentials&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
resource ExternalCredentialsResponse POST &lt;span class="s2"&gt;"/external/{provider}/domain/{domainName}/creds"&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    SimpleName provider&lt;span class="p"&gt;;&lt;/span&gt; //provider name to request credentials from
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/blob/master/core/zts/src/main/rdl/ExternalCredentials.rdli#L22" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/blob/master/core/zts/src/main/rdl/ExternalCredentials.rdli#L22&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And NOT &lt;code&gt;athenzRole&lt;/code&gt; but:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;public static final String ZTS_EXTERNAL_ATTR_ROLE_NAME     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"athenzRoleName"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/blob/4af98e4a2a338c638c44a30f0322e6ee328c953e/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java#L248" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/blob/4af98e4a2a338c638c44a30f0322e6ee328c953e/servers/zts/src/main/java/com/yahoo/athenz/zts/ZTSConsts.java#L248&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?
&lt;/h3&gt;

&lt;p&gt;Not efficient as I do not have Azure subscription.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I learned
&lt;/h3&gt;

&lt;p&gt;I have heard that Yahoo Inc uses the Athenz as a SSOT for login for other PFs but learned that it includes for Azure too, implemented in &lt;a href="https://github.com/AthenZ/athenz/pull/2634" rel="noopener noreferrer"&gt;Jonmv/assume azure services #2634&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    autonumber
    actor User as Athenz Client (User/Service)
    participant ZTS as Athenz ZTS Server
    participant AzureARM as Azure Resource Manager
    participant AzureAD as Azure AD (Entra ID)

    Note over User, ZTS: 1. Request Token
    User-&amp;gt;&amp;gt;ZTS: POST /external/azure/.../creds&amp;lt;br/&amp;gt;{Role: "azure-log-reader",&amp;lt;br/&amp;gt;IdentityName: "log-reader",&amp;lt;br/&amp;gt;ResourceGroup: "system"}

    Note over ZTS, AzureARM: 2. Lookup Identity Info (Name -&amp;gt; Client ID)
    ZTS-&amp;gt;&amp;gt;AzureARM: Query Client ID for "log-reader" in Resource Group&amp;lt;br/&amp;gt;(Using ZTS's own Azure Identity permissions)
    AzureARM--&amp;gt;&amp;gt;ZTS: Returns Client ID (UUID)

    Note over ZTS, AzureAD: 3. Token Exchange (Federation)
    ZTS-&amp;gt;&amp;gt;ZTS: Generate ID Token&amp;lt;br/&amp;gt;(iss: ZTS, sub: "coretech:role.azure-log-reader")
    ZTS-&amp;gt;&amp;gt;AzureAD: Submit ID Token &amp;amp; Request Access Token&amp;lt;br/&amp;gt;(aud: api://AzureADTokenExchange)
    AzureAD-&amp;gt;&amp;gt;AzureAD: Validate Federated Credential&amp;lt;br/&amp;gt;(Check Issuer &amp;amp; Subject match)
    AzureAD--&amp;gt;&amp;gt;ZTS: Issues Azure Access Token

    Note over ZTS, User: 4. Final Response
    ZTS--&amp;gt;&amp;gt;User: Returns Azure Access Token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;classDiagram
    direction LR

    class AthenzDomain {
        Name: coretech
        AzureSubscription: (ID)
        AzureTenant: (ID)
    }

    class AthenzRole {
        Name: azure-log-reader
        FullARN: coretech:role.azure-log-reader
        Members: (Users/Services)
    }

    class AzureManagedIdentity {
        Name: log-reader
        ResourceGroup: system
        ClientID: (UUID)
    }

    class FederatedCredential {
        Issuer: &amp;lt;ZTS API URL&amp;gt;
        Subject: coretech:role.azure-log-reader
        Audience: api://AzureADTokenExchange
    }

    %% Relationship Definitions
    AthenzDomain "1" *-- "many" AthenzRole : contains
    AzureManagedIdentity "1" *-- "1" FederatedCredential : contains config

    AthenzRole .. FederatedCredential : 1. Mapping (Subject Match)
    FederatedCredential .. AzureManagedIdentity : 2. Grant Access (Allows usage of this ID)

    note for FederatedCredential "Key Connector:\nAzure grants access based on this credential\nwhen a specific Athenz Role requests it."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  PR: use metadata to specify use of default identity #3084
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3084" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3084&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GCP Service Account Name must be at least 6 characters long&lt;/li&gt;
&lt;li&gt;Yahoo Inc has many services that are shorter than 6 characters, like &lt;code&gt;zts&lt;/code&gt;, &lt;code&gt;zms&lt;/code&gt;, &lt;code&gt;msd&lt;/code&gt; already&lt;/li&gt;
&lt;li&gt;GCP instances allow you to specify metadata of any key-value pairs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm14x18aqjuux08x87iu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm14x18aqjuux08x87iu5.png" alt="gcp_sa_at_least_6_chars" width="800" height="666"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;Allows GCP users to set a new metadata key &lt;code&gt;defaultServiceIdentity&lt;/code&gt; in their GCP instances. This maps to an Athenz service name that differs from the native GCP service account name, allowing them to use their short Athenz service names (like &lt;code&gt;zts&lt;/code&gt; or &lt;code&gt;zms&lt;/code&gt;, shorter than 6 characters).&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request introduces a mechanism to use the instance's default identity for a service by specifying a defaultServiceIdentity metadata attribute. This is useful when the desired service name doesn't match the GCP service account name. The changes in attestation.go implement this logic, and new tests are added in attestation_test.go to cover the new functionality. My review focuses on improving the robustness of the new logic and correcting issues in the new tests. I've suggested handling a potential edge case with empty service names and improving error visibility in the main logic. For the tests, I've pointed out a logic bug that prevents a test from failing correctly and recommended using test-specific logging for cleaner output. Overall, the changes are good and the tests are comprehensive, but these adjustments will improve the quality and maintainability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: Yes, but skipped
&lt;/h3&gt;

&lt;p&gt;Probably yes, but I would need to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a GCP instance&lt;/li&gt;
&lt;li&gt;Setup an Athenz service that is shorter than 6 characters&lt;/li&gt;
&lt;li&gt;Setup GCP instance metadata to use the short service name with the new key &lt;code&gt;defaultServiceIdentity&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Which I don't think is easy to do by myself. Since I understand the concept, I think it's fine to skip it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Potentials
&lt;/h3&gt;

&lt;p&gt;If we ever face a situation where we need to use a short service name for any other 3rd party systems, we can consider to have a alias stored inside the instance metadata.&lt;/p&gt;




&lt;h2&gt;
  
  
  PR: Make ZpeUpdPolLoader ScheduledExecutorService thread daemon #3086
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3086" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3086&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1
&lt;/h4&gt;

&lt;p&gt;In Java, there are two types of threads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Non-Daemon Thread&lt;/code&gt;: A thread that runs in the foreground and is essential to the application's execution. It is like a head chef in a restaurant. (Which also implies you usually do not shutdown your restraunt when your head chef is still cooking (running))&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Daemon Thread&lt;/code&gt;: A thread that runs in the background and is not essential to the application's execution. It is like a background music in a restaurant. (Which also implies you can shutdown your restraunt even when the background music is still playing)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Platform threads are designated daemon or non-daemon threads.&lt;/em&gt;&lt;br&gt;
— &lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Thread.html#platform-threads" rel="noopener noreferrer"&gt;Class Thread - Oracle Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  2
&lt;/h4&gt;

&lt;p&gt;Starting of Java 21 (Sep 2023~), there are two types of threads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Platform threads&lt;/code&gt;: The threads that we are familiar with, which are mapped to OS threads.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Virtual threads&lt;/code&gt;: Lightweight threads introduced in Java 21 that are managed by the JVM and are not mapped to OS threads. Can run millions of them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
Please note that &lt;code&gt;Daemon Thread&lt;/code&gt; does not initiate shutdown sequence by itself.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The JVM initiates the &lt;strong&gt;shutdown sequence&lt;/strong&gt; in response to &lt;em&gt;one&lt;/em&gt; of several events:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;when the number of live non-daemon threads drops to zero for the first time&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;when the Runtime.exit or System.exit method is called for the first time&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;when some external event occurs, such as an interrupt or a signal is received from the operating system&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;— &lt;a href="https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Runtime.html#shutdown" rel="noopener noreferrer"&gt;Shutdown Sequence - Class Runtime - Oracle Docs&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then it does for any daemon or non-daemon threads:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;the registered shutdown hooks are started in some unspecified order.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The shutdown sequence finishes when all shutdown hooks have terminated.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And note that:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;one or more shutdown hooks do not terminate, for example, because of an infinite loop. In this case, the shutdown sequence will never finish.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  4
&lt;/h4&gt;

&lt;p&gt;Since Java's shutdown sequence can be initiated with external requests as well, it is important to make your threads to have a safe shutdown method like &lt;code&gt;close()&lt;/code&gt; or &lt;code&gt;shutdown()&lt;/code&gt; that will be running in the &lt;code&gt;registered shutdown hooks&lt;/code&gt; stage, to prevent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;dead lock for your relying DB services&lt;/li&gt;
&lt;li&gt;resource leak&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  5
&lt;/h4&gt;

&lt;p&gt;Relying users uses &lt;code&gt;AuthZpeClient.allowAccess()&lt;/code&gt; to determine if certain requests are allowed or not. &lt;code&gt;AuthZpeClient&lt;/code&gt; uses &lt;code&gt;ZpeUpdPolLoader&lt;/code&gt; to load policies from ZMS. &lt;code&gt;ZpeUpdPolLoader&lt;/code&gt; load policies by creating &lt;code&gt;ScheduledExecutorService&lt;/code&gt; thread behind the scene (without letting the relying users know it) to load policies from ZMS periodically. &lt;code&gt;ZpeUpdPolLoader&lt;/code&gt; loads policy over asking every time is for ZMS's over load protection &amp;amp; distributed access check despite of ZMS's availability.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request addresses a resource management issue where the &lt;code&gt;ScheduledExecutorService&lt;/code&gt; in &lt;code&gt;ZpeUpdPolLoader&lt;/code&gt; could prevent graceful application shutdown by keeping &lt;code&gt;non-daemon threads&lt;/code&gt; alive. The change modifies the executor's thread creation to mark them as daemon threads and provides them with explicit names, ensuring they do not block the JVM exit and improving troubleshooting capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;Make &lt;code&gt;ScheduledExecutorService&lt;/code&gt; &lt;code&gt;non-daemon thread&lt;/code&gt; =&amp;gt; &lt;code&gt;daemon thread&lt;/code&gt; so that it does not require to &lt;code&gt;close()&lt;/code&gt; thread that is initiated by the library - which developers of the library do not know it exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: Yes
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
Does not require Athenz server to be up &amp;amp; running.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Setup: Namespace &lt;code&gt;athenz&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create ns athenz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Setup: Java code as a CM
&lt;/h4&gt;

&lt;p&gt;Create a configmap with &lt;code&gt;athenz zpe java client&lt;/code&gt; &amp;amp; its code that uses &lt;code&gt;ZpeUpdPolLoader&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;cat &amp;lt;&amp;lt; 'EOF' | kubectl apply -f -&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;athenz-thread-test-code&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;athenz&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pom.xml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;&amp;lt;project&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;groupId&amp;gt;demo&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;artifactId&amp;gt;repro&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;version&amp;gt;1.0&amp;lt;/version&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;properties&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;&amp;lt;maven.compiler.source&amp;gt;17&amp;lt;/maven.compiler.source&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;&amp;lt;maven.compiler.target&amp;gt;17&amp;lt;/maven.compiler.target&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;/properties&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;&amp;lt;groupId&amp;gt;com.yahoo.athenz&amp;lt;/groupId&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;&amp;lt;artifactId&amp;gt;athenz-zpe-java-client&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;&amp;lt;version&amp;gt;${athenz.version}&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
            &lt;span class="s"&gt;&amp;lt;groupId&amp;gt;org.codehaus.mojo&amp;lt;/groupId&amp;gt;&lt;/span&gt;
            &lt;span class="s"&gt;&amp;lt;artifactId&amp;gt;exec-maven-plugin&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
            &lt;span class="s"&gt;&amp;lt;version&amp;gt;3.1.0&amp;lt;/version&amp;gt;&lt;/span&gt;
            &lt;span class="s"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
              &lt;span class="s"&gt;&amp;lt;mainClass&amp;gt;demo.Repro&amp;lt;/mainClass&amp;gt;&lt;/span&gt;
            &lt;span class="s"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
          &lt;span class="s"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;

  &lt;span class="na"&gt;Repro.java&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;package demo;&lt;/span&gt;
    &lt;span class="s"&gt;import com.yahoo.athenz.zpe.ZpeUpdater;&lt;/span&gt;
    &lt;span class="s"&gt;import java.nio.file.Files;&lt;/span&gt;
    &lt;span class="s"&gt;import java.nio.file.Path;&lt;/span&gt;

    &lt;span class="s"&gt;public class Repro {&lt;/span&gt;
      &lt;span class="s"&gt;public static void main(String[] args) throws Exception {&lt;/span&gt;
        &lt;span class="s"&gt;// Dummy environment settings to prevent errors during ZPE initialization&lt;/span&gt;
        &lt;span class="s"&gt;System.setProperty("athenz.zpe.skip_policy_dir_check", "true");&lt;/span&gt;
        &lt;span class="s"&gt;Path tmp = Files.createTempDirectory("athenz-zpe-pol");&lt;/span&gt;
        &lt;span class="s"&gt;System.setProperty("athenz.zpe.policy_dir", tmp.toString());&lt;/span&gt;

        &lt;span class="s"&gt;System.out.println("[INFO] Initializing ZpeUpdater...");&lt;/span&gt;
        &lt;span class="s"&gt;// This line creates a scheduler thread in the background.&lt;/span&gt;
        &lt;span class="s"&gt;ZpeUpdater zpe = new ZpeUpdater(); &lt;/span&gt;

        &lt;span class="s"&gt;Thread.sleep(1000); // Wait for thread creation&lt;/span&gt;

        &lt;span class="s"&gt;Thread.getAllStackTraces().keySet().stream()&lt;/span&gt;
            &lt;span class="s"&gt;.filter(t -&amp;gt; t.isAlive() &amp;amp;&amp;amp; !t.isDaemon())&lt;/span&gt;
            &lt;span class="s"&gt;.forEach(t -&amp;gt; System.out.println("  - " + t.getName()));&lt;/span&gt;

        &lt;span class="s"&gt;System.out.println("[INFO] main() finishes here. No explicit close() called.");&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify: v1.12.26 vs v1.12.27
&lt;/h4&gt;

&lt;p&gt;You can see that the pod with Athenz v1.12.27+ shutdowns and completes successfully, while the pod with Athenz v1.12.26+ hangs there keep in &lt;code&gt;Running&lt;/code&gt; state:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcjnye9w6p8xlw3z32gj.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjcjnye9w6p8xlw3z32gj.gif" alt="zpu_daemon_test" width="600" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To verify it yourself, you can create a pod that runs the java code with &lt;code&gt;athenz-zpe-java-client&lt;/code&gt; version &lt;code&gt;1.12.26&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run test-bug-1-12-26 &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;maven:3.9-eclipse-temurin-17 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--overrides&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{
    "spec": {
      "containers": [{
        "name": "test",
        "image": "maven:3.9-eclipse-temurin-17",
        "command": ["/bin/sh", "-c"],
        "args": ["mkdir -p /app/src/main/java/demo &amp;amp;&amp;amp; cp /config/pom.xml /app/ &amp;amp;&amp;amp; cp /config/Repro.java /app/src/main/java/demo/ &amp;amp;&amp;amp; cd /app &amp;amp;&amp;amp; mvn -q compile exec:java -Dathenz.version=1.12.26"],
        "volumeMounts": [{"name": "code-vol", "mountPath": "/config"}]
      }],
      "volumes": [{"name": "code-vol", "configMap": {"name": "athenz-thread-test-code"}}]
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then compare with the pod that runs the java code with &lt;code&gt;athenz-zpe-java-client&lt;/code&gt; version &lt;code&gt;1.12.27&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run test-fix-1-12-27 &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;maven:3.9-eclipse-temurin-17 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;athenz &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--overrides&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{
    "spec": {
      "containers": [{
        "name": "test",
        "image": "maven:3.9-eclipse-temurin-17",
        "command": ["/bin/sh", "-c"],
        "args": ["mkdir -p /app/src/main/java/demo &amp;amp;&amp;amp; cp /config/pom.xml /app/ &amp;amp;&amp;amp; cp /config/Repro.java /app/src/main/java/demo/ &amp;amp;&amp;amp; cd /app &amp;amp;&amp;amp; mvn -q compile exec:java -Dathenz.version=1.12.27"],
        "volumeMounts": [{"name": "code-vol", "mountPath": "/config"}]
      }],
      "volumes": [{"name": "code-vol", "configMap": {"name": "athenz-thread-test-code"}}]
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Future Potentials
&lt;/h3&gt;

&lt;p&gt;Not for this specific improvement, but I might be able to spot similar thread-related problems in the future.&lt;/p&gt;




&lt;h2&gt;
  
  
  PR: make otel metric options more configurable #3090
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3090" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3090&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1
&lt;/h4&gt;

&lt;p&gt;Otel offers three metrics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;counter: total amount of requests + total number of errors etc&lt;/li&gt;
&lt;li&gt;gauge: memory usage, current live users&lt;/li&gt;
&lt;li&gt;histogram: latency (heavy with its nature)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike logs, Otel stores its metrics in memory with unique label combination, for example:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Label (Time Series)&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;athenz_api_request_duration_seconds{api="getDomain", domain="sys.auth", method="GET", status="200"}&lt;/td&gt;
&lt;td&gt;0.045&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;athenz_api_request_duration_seconds{api="getDomain", domain="my.custom.domain", method="GET", status="200"}&lt;/td&gt;
&lt;td&gt;0.051&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The problem of this label above is that production level can have many domains (some internal case has 200,000 domains), and the otel will suffer from OOM due this massive number of combination. We call it as "Cardinality Explosion".&lt;/p&gt;

&lt;h4&gt;
  
  
  2
&lt;/h4&gt;

&lt;p&gt;What is Cardinality?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cardinality&lt;/strong&gt; refers to the number of unique combinations of labels (tags) attached to a metric. In time-series databases (like Prometheus or Datadog), every unique combination creates a brand new, separate data stream called a &lt;strong&gt;Time Series&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  3
&lt;/h4&gt;

&lt;p&gt;What is then "Cardinality Explosion"?&lt;/p&gt;

&lt;p&gt;The number of time series grows exponentially by multiplying the number of possible values for each label.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Safe:&lt;/strong&gt; &lt;code&gt;Method&lt;/code&gt; (GET, POST = 2) × &lt;code&gt;Status&lt;/code&gt; (200, 400, 500 = 3)
👉 Total &lt;strong&gt;6&lt;/strong&gt; time series. (Easy to manage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;💥 Explosion:&lt;/strong&gt; Add &lt;code&gt;Domain&lt;/code&gt; (10,000 user domains)
👉 2 × 3 × 10,000 = Total &lt;strong&gt;60,000&lt;/strong&gt; time series!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why is it dangerous?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The monitoring server has to keep a separate "memory bucket" active for every single time series. A sudden explosion in cardinality will cause:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;OOM (Out of Memory) Crashes:&lt;/strong&gt; Your monitoring server (e.g., Prometheus) runs out of RAM and dies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow Queries:&lt;/strong&gt; Grafana dashboards will freeze or time out while trying to load the massive number of series.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Massive Bills:&lt;/strong&gt; Cloud monitoring tools like Datadog charge by the number of custom metrics/series. It can lead to a huge unexpected bill.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Golden Rule:&lt;/strong&gt; Never use labels with unbounded or highly variable values (like User IDs, IP addresses, or highly diverse Domain names) in your metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
Histogram data (latency) is heavy and expensive. If you enable the separate option, skipping the domain-specific data is highly recommended to save resources. That is why the default for &lt;code&gt;skip_domain_histogram&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;For Histogram (&lt;code&gt;athenz.otel_separate_domain_histogram_metrics&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;false&lt;/code&gt; (default): Creates a single "fat" metric containing all labels, including domain names. (High risk of cardinality explosion)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;true&lt;/code&gt;: Separates the metric into 3 distinct metrics (Core API metric, &lt;code&gt;_requestDomain&lt;/code&gt;, and &lt;code&gt;_principalDomain&lt;/code&gt;).

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;athenz.otel_skip_domain_histogram_metrics&lt;/code&gt;:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;true&lt;/code&gt; (default): Skips recording the two domain-specific metrics. Only the core metric (with &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;method&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;) is recorded. (Maximum cost efficiency, but no domain-level latency tracking)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;false&lt;/code&gt;: Records all 3 separated metrics. (Moderate cost efficiency, retains domain-level latency tracking)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;For Counter (&lt;code&gt;athenz.otel_separate_domain_counter_metrics&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;false&lt;/code&gt; (default): Creates a single "fat" metric containing all labels, including domain names.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;true&lt;/code&gt;: Separates the metric into 3 distinct metrics (Core API metric, &lt;code&gt;_requestDomain&lt;/code&gt;, and &lt;code&gt;_principalDomain&lt;/code&gt;).

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;athenz.otel_skip_domain_counter_metrics&lt;/code&gt;:&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;false&lt;/code&gt; (default): Records all 3 separated metrics. (Reduces core cardinality while still tracking domain-level request counts)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;true&lt;/code&gt;: Skips recording the two domain-specific metrics. Only the core metric is recorded. (Maximum cost efficiency, but no domain-level count tracking)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request enhances the configurability of OpenTelemetry metrics by separating the settings for histogram and counter metrics, and adding options to skip domain-specific metrics to control cardinality. The changes are logical and well-implemented, with corresponding updates to tests.&lt;/p&gt;

&lt;p&gt;A key point to consider is that renaming the configuration property &lt;code&gt;athenz.otel_separate_domain_metrics&lt;/code&gt; to &lt;code&gt;athenz.otel_separate_domain_histogram_metrics&lt;/code&gt; constitutes a breaking change for users who might have the old property configured. It would be beneficial to update the pull request description to acknowledge this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Changes?: Yes
&lt;/h3&gt;

&lt;p&gt;Users using &lt;code&gt;athenz.otel_separate_domain_metrics&lt;/code&gt; will need to update their configuration to &lt;code&gt;athenz.otel_separate_domain_histogram_metrics&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: Yes, but skipped
&lt;/h3&gt;

&lt;p&gt;It seems like Athenz by defaut has the library, so all I have to do is to apply the config change to the cluster, and run something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl localhost:8080/metrics | &lt;span class="nb"&gt;grep &lt;/span&gt;athenz_api_request_duration_seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But skipped for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Potentials
&lt;/h3&gt;

&lt;p&gt;Once we deploy the &lt;code&gt;otel&lt;/code&gt; support, this knowledge will be very benefitial.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR: expose openid_issuer field for access tokens in zts java client #3091
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3091" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3091&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;AWS EKS can be configured to use &lt;code&gt;Athenz&lt;/code&gt; as &lt;code&gt;OIDC Authentication Provider&lt;/code&gt; to authorize access to configured EKS clusters&lt;/p&gt;

&lt;p&gt;&lt;a href="https://athenz.github.io/athenz/oidc_aws_eks/" rel="noopener noreferrer"&gt;https://athenz.github.io/athenz/oidc_aws_eks/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ZTS has &lt;code&gt;openid_issuer&lt;/code&gt; field for AccessToken Request if you want the AccessToken with issuer field for openid&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;ZTS Java Client has not supported any parameters to request for &lt;code&gt;openid_issuer=true&lt;/code&gt; agasint ZTS. This PR allows you to do so so that your Java application may connect to Athenz protected AWS EKS.&lt;/p&gt;

&lt;p&gt;Also they introduced &lt;code&gt;AccessTokenRequestBuilder&lt;/code&gt; so that can set default behavior and never pass it as a parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request successfully exposes the &lt;code&gt;openid_issuer&lt;/code&gt; field for access tokens in the ZTS Java client. The introduction of the AccessTokenRequestBuilder is a great design choice to avoid further overloading the getAccessToken method, improving the client's usability and maintainability. The changes are well-implemented, and the tests have been updated accordingly to cover the new functionality, including a new comprehensive test for &lt;code&gt;PrefetchTokenScheduledItem&lt;/code&gt;. My review includes one suggestion to improve the maintainability of a newly added test method by refactoring it into smaller, more focused tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Changes?: No
&lt;/h3&gt;

&lt;p&gt;No, just a feature that had been missing; not a bug but more of missing core feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: Yes, but skipped
&lt;/h3&gt;

&lt;p&gt;I think I could test getting the AccessToken with &lt;code&gt;openid_issuer=true&lt;/code&gt; withmy own Java Code to test it, but I skipped it for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Potentials
&lt;/h3&gt;

&lt;p&gt;If I ever work with &lt;code&gt;Athenz &amp;lt;=&amp;gt; AWS EKS&lt;/code&gt;, I might be able to use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR: Add FreeBSD support to libs/go/sia/util #3093
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3093" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3093&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Glossary
&lt;/h3&gt;

&lt;p&gt;What is FreeBSD?&lt;/p&gt;

&lt;p&gt;An OS great for networking, a direct descendant of &lt;em&gt;Unix&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;└── unix
    ├── BSD (by UC Berkly in 1970s)
    │   └── MacOS
    │   └── FreeBSD
    │           └── Netflix
    │           └── Uber
    ├── Linux (Only Influenced Though)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is Vendor Build?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Normal Build&lt;/code&gt;: Every build fetches dependencies from the internet (versions may differ).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Vendor Build&lt;/code&gt;: Allows you to build using local ingredients. It's always the same because the local ingredients never change, making it much safer and guaranteed to work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;OSS SIA allows you to vendor-build for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;darwin&lt;/li&gt;
&lt;li&gt;linux&lt;/li&gt;
&lt;li&gt;windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;but has not yet supported &lt;code&gt;freebsd&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;It now supports vendor builds for &lt;code&gt;freebsd&lt;/code&gt; as well, using &lt;code&gt;libs/go/sia/util/os_util_freebsd.go&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request adds support for FreeBSD by introducing os_util_freebsd.go, which is a good step towards broader platform compatibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Changes?: No
&lt;/h3&gt;

&lt;p&gt;Enhancement; you can now vendor-build on &lt;code&gt;FreeBSD&lt;/code&gt; OS too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: Yes, but skipped
&lt;/h3&gt;

&lt;p&gt;I need to prepare &lt;code&gt;FreeBSD&lt;/code&gt; &amp;amp; Try to vendor-build. Skipped for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Potentials
&lt;/h3&gt;

&lt;p&gt;If we want to vendor-build somewhere else, I think I can work on it with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR: expose x509/ssh key id for instance register/refresh operations #3092
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3092" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3092&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
The same as &lt;code&gt;Description&lt;/code&gt; in &lt;a href="https://github.com/AthenZ/athenz/pull/3092" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3092&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can fetch X.509 cert with custom CA for specific:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Athenz domain&lt;/li&gt;
&lt;li&gt;Athenz service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This however requires system admin to set it.&lt;/p&gt;

&lt;p&gt;Also note that Vespa used Yahoo Inc.'s Athenz domain and wanted to use their custom CA, so they allowed Vespa-owned domains to have custom CAs. However, adding custom CAs for a specific domain/service can only be done by system admins.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;This PR allows a service admin (not a system admin) to test a custom or default CA. They no longer have to ask system admins to modify CA settings just for testing purposes, allowing them to try different CAs without changing the custom CA setting for the domain/service permanently.&lt;/p&gt;

&lt;p&gt;Non-system admins can test it by inclduing new field:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x509CertSignerKeyId&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sshCertSignerKeyId&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also allows a domain using a custom CA to connect to services that only accept the default CA. It does this by letting them request a cert signed by the default CA, potentially allowing a single service to operate with both a default and custom CA.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request successfully exposes x509/ssh key IDs for instance register/refresh and role certificate operations, enhancing testing capabilities for service admins. The changes across the Go and Java codebases are mostly well-implemented and consistent. I've identified a few opportunities for improvement, primarily around reducing code duplication in both Go validation logic and the new Java tests. Additionally, I've found a minor bug in one of the new tests that should be addressed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Changes?: No
&lt;/h3&gt;

&lt;p&gt;More like enhancement: allow you to submit extra field for &lt;code&gt;InstanceRefreshInformation&lt;/code&gt; or &lt;code&gt;InstanceRegisterInformation&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: Yes, but skipped
&lt;/h3&gt;

&lt;p&gt;I think I can register a CA for a specific athenz service, and try to generate X.509 cert signed by the specific CA, but skipped for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Potentials
&lt;/h3&gt;

&lt;p&gt;If one of our services spinned out, maybe yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  PR: fix util test os filenames + new GetGroupGID impl #3094
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3094" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3094&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;SIA wants to ensure that the outputted cert files have the correct &lt;code&gt;GroupID&lt;/code&gt;. SIA builders can specify a &lt;code&gt;group name&lt;/code&gt; (not a &lt;code&gt;GroupID&lt;/code&gt;; defaults to athenz if unset) to allow members of that group to read the cert file. SIA reads the /etc/group file to find the GID matching the provided group name, ensuring the outputted cert file is assigned the correct GroupID.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;Uses the standard Go package &lt;code&gt;os.Read()&lt;/code&gt; to parse &lt;code&gt;/etc/group&lt;/code&gt; data instead of relying on the &lt;code&gt;grep&lt;/code&gt; command. This makes it more robust and cross-platform compatible across different OSes.&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request significantly refactors the group ID retrieval mechanism within the utility package by introducing a new GetGroupGID function that directly reads and parses the &lt;code&gt;/etc/group&lt;/code&gt; file. This change moves away from executing external &lt;code&gt;grep&lt;/code&gt; commands, enhancing the robustness and cross-platform compatibility of the group lookup logic. Additionally, the PR includes important fixes to OS-specific test file names and expands the test suite for the updated group ID retrieval functionality, ensuring comprehensive validation of the new implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Changes?: No
&lt;/h3&gt;

&lt;p&gt;It simply makes it better reading the &lt;code&gt;/etc/group&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: Yes, but skipped
&lt;/h3&gt;

&lt;p&gt;I think I can create an instance with &lt;code&gt;grep&lt;/code&gt; removed and try to build the sia package, which is expected to fail.&lt;/p&gt;

&lt;p&gt;And then this enhanceement will allow me to build it because it no longer uses the &lt;code&gt;grep&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Potentials
&lt;/h3&gt;

&lt;p&gt;A good reminder that using standard Go libraries is much better than depending on external OS packages.&lt;/p&gt;

&lt;p&gt;External packages might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deleted&lt;/li&gt;
&lt;li&gt;modified&lt;/li&gt;
&lt;li&gt;or differ between OSes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's dangerous to rely on them for important data outputs, so internal handling is always safer.&lt;/p&gt;




&lt;h2&gt;
  
  
  PR: update go and java dependencies to their latest releases #3095
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3095" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3095&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Athenz used to handle validation internally because its dependency package didn't enforce email validation in &lt;code&gt;libs/go/athenzutils/principal.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// athenz always verifies that we include a valid&lt;/span&gt;
&lt;span class="c"&gt;// email in the certificate&lt;/span&gt;

&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"@"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"certificate email is invalid: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;A periodic package upgrade. One of the upgrades includes &lt;code&gt;net/mail&lt;/code&gt;'s stricter parsing of email addresses, meaning we no longer need to maintain internal logic to test email validation.&lt;/p&gt;

&lt;p&gt;While reading this PR, I realized that the internal email check might no longer be needed at all. I wasn't completely sure, so I submitted a PR to get feedback: &lt;a href="https://github.com/AthenZ/athenz/pull/3226" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3226&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request updates various Go and Java dependencies to their latest versions. The changes primarily consist of version bumps in &lt;code&gt;go.mod&lt;/code&gt;, &lt;code&gt;go.sum&lt;/code&gt;, and &lt;code&gt;pom.xml&lt;/code&gt;. A key consequence of these updates is the removal of Go test cases that relied on a certificate with an invalid email format. As noted in the description, stricter parsing in newer libraries now causes these tests to fail during certificate loading, making this a reasonable and well-documented adjustment. The dependency updates are beneficial for project maintenance and security. The changes appear correct and well-justified.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Changes?: No
&lt;/h3&gt;

&lt;p&gt;No breaking change, the same email invalid checking logic, simply done by different entity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: Yes, but skipped
&lt;/h3&gt;

&lt;p&gt;I think I can prepare one certificate with invalid email address in it, and send it to Athenz server, but skipped for now.&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Potential
&lt;/h3&gt;

&lt;p&gt;If we ever upgrade the package to 1.25.2+ from other package, the stricker checking mechanism may bring breaking changes&lt;/p&gt;




&lt;h2&gt;
  
  
  PR: allow wildcard in first domain component of StaticWorkloadName #3096
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AthenZ/athenz/pull/3096" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/pull/3096&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Glossary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;StaticWorkload&lt;/code&gt;: The opposite of a &lt;code&gt;ServiceIdentity&lt;/code&gt; (e.g., &lt;code&gt;IP Address&lt;/code&gt;, &lt;code&gt;FQDN&lt;/code&gt;, &lt;code&gt;VIP&lt;/code&gt;, &lt;code&gt;LB&lt;/code&gt;, &lt;code&gt;NAT&lt;/code&gt;). These are resources you don't name using an Athenz Service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before this PR, if you want to include &lt;code&gt;StaticWorkloadFQDN&lt;/code&gt; for the following domains, you had to add them all individually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep.google.com&lt;/li&gt;
&lt;li&gt;docs.google.com&lt;/li&gt;
&lt;li&gt;drive.google.com&lt;/li&gt;
&lt;li&gt;mail.google.com&lt;/li&gt;
&lt;li&gt;and more&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  PR Summary in my own words
&lt;/h3&gt;

&lt;p&gt;This PR allows you to use &lt;code&gt;*&lt;/code&gt; (wildcard) for the static workload's name like &lt;code&gt;*.google.com&lt;/code&gt; to include subdomains.&lt;/p&gt;

&lt;p&gt;Note that for stricter checking, it only allows you to use the &lt;code&gt;*&lt;/code&gt; only once and it must be at the front of the domain name.&lt;/p&gt;

&lt;p&gt;You can see as &lt;a href="https://github.com/AthenZ/athenz/blob/de4d03592ffd0ab11bc86b50b1af23b0aa97acae/core/msd/src/test/java/com/yahoo/athenz/msd/StaticWorkloadTest.java#L122-L148" rel="noopener noreferrer"&gt;a sample from test&lt;/a&gt; to see what evaluates to &lt;code&gt;true&lt;/code&gt; (allowed) or &lt;code&gt;false&lt;/code&gt; (not allowed).&lt;/p&gt;

&lt;p&gt;It also silently added a &lt;a href="https://github.com/AthenZ/athenz/blob/master/CLAUDE.md" rel="noopener noreferrer"&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/a&gt; for AIs&lt;/p&gt;

&lt;h3&gt;
  
  
  PR Summary from AI
&lt;/h3&gt;

&lt;p&gt;This pull request introduces the ability to use a wildcard character (*) in the first domain component of a &lt;code&gt;StaticWorkloadName&lt;/code&gt;. This change is implemented across the RDL definition, Java schema, and Go client schema for the MSD (Microservice Daemon) component. Additionally, a new CLAUDE.md file has been added to provide guidance for AI assistants working with the repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Breaking Changes?: No
&lt;/h3&gt;

&lt;p&gt;Nope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I test it out?: No
&lt;/h3&gt;

&lt;p&gt;MSD is not yet open sourced&lt;/p&gt;

&lt;h3&gt;
  
  
  Future Potential
&lt;/h3&gt;

&lt;p&gt;If we ever use the MSD, I think I can think of the &lt;code&gt;*&lt;/code&gt; usage for the &lt;code&gt;StaticWorkloadFQDN&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;Reading Athenz Note definitely helped me out understanding the recent changes. I will continue doing this for later releases as well.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing
&lt;/h1&gt;

&lt;p&gt;If you enjoyed this deep dive, please leave a like &amp;amp; subscribe for more!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle5ltrouo6ig6y64liak.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle5ltrouo6ig6y64liak.png" alt="like_this_photo_cat" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>athenz</category>
    </item>
    <item>
      <title>[Post-Mortem] Deploying Athenz with Okta Sign-In: A Partial Success</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Sat, 21 Feb 2026 22:54:20 +0000</pubDate>
      <link>https://forem.com/mlajkim/post-mortem-deploying-athenz-with-okta-sign-in-a-partial-success-2ji2</link>
      <guid>https://forem.com/mlajkim/post-mortem-deploying-athenz-with-okta-sign-in-a-partial-success-2ji2</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
In hurry? Jump directly to Result section to see the outcome of this dive.&lt;/p&gt;

&lt;p&gt;[!NOTE]&lt;br&gt;
Although I am organizing and writing this on 2/21 after my LF AI &amp;amp; Data Japan RUG presentation has successfully concluded, I believe it is crucial to leave a record of failures and roadblocks. Therefore, I am documenting this past dive retrospectively.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal of this dive is to integrate Okta Sign-In with the Athenz ecosystem, by achieving the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Login into Athenz UI with Okta SSO&lt;/li&gt;
&lt;li&gt;Successfully run &lt;code&gt;zms-cli&lt;/code&gt; with Okta SSO&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  ToC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Goal&lt;/li&gt;
&lt;li&gt;ToC&lt;/li&gt;
&lt;li&gt;Result&lt;/li&gt;
&lt;li&gt;
Setup

&lt;ul&gt;
&lt;li&gt;Setup: Sign up Okta&lt;/li&gt;
&lt;li&gt;Setup: Okta with Work Email&lt;/li&gt;
&lt;li&gt;Setup: Okta Verify for Mac OS locally&lt;/li&gt;
&lt;li&gt;Test: Login Okta&lt;/li&gt;
&lt;li&gt;Setup: Okta App Integration&lt;/li&gt;
&lt;li&gt;Setup: Okta Authorization Server API&lt;/li&gt;
&lt;li&gt;Setup: Okta Policy &amp;amp; Rules&lt;/li&gt;
&lt;li&gt;Test: Token Preview&lt;/li&gt;
&lt;li&gt;Setup: &lt;code&gt;sub&lt;/code&gt; as athenz user service name&lt;/li&gt;
&lt;li&gt;Test: &lt;code&gt;sub&lt;/code&gt; from Token Preview&lt;/li&gt;
&lt;li&gt;Setup: Access Token&lt;/li&gt;
&lt;li&gt;Test: Access Token&lt;/li&gt;
&lt;li&gt;Setup: Adding ZMS properties so that ZMS can trust the okta verify&lt;/li&gt;
&lt;li&gt;Setup: Make sure to restart the ZMS server to get the changes!&lt;/li&gt;
&lt;li&gt;Setup: Set k8s secret for proxy credentials&lt;/li&gt;
&lt;li&gt;Setup: Create proxy container&lt;/li&gt;
&lt;li&gt;Setup: Remove &lt;code&gt;STATIC_USER_NAME&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Setup: Modify the UI config&lt;/li&gt;
&lt;li&gt;Verify: Does it work?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;What I learned&lt;/li&gt;

&lt;li&gt;What's next?&lt;/li&gt;

&lt;li&gt;Closing&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Result
&lt;/h1&gt;

&lt;p&gt;I was able to login into Athenz UI successfully using Okta SSO.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ybuhk1m40e0xmdksd64.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0ybuhk1m40e0xmdksd64.png" alt="login_as_ajkim_ajktown" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, due to the complexity of Identity mapping, I couldn't achieve the following goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Acting normally as the mapped user &lt;code&gt;user.ajkim&lt;/code&gt; post-login. (The system authenticates me as the full email &lt;code&gt;ajkim@ajktown.com&lt;/code&gt;, failing authorization checks).&lt;/li&gt;
&lt;li&gt;Logging into &lt;code&gt;zms-cli&lt;/code&gt; directly using the Okta integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Setup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Setup: Sign up Okta
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ggsb1rhb3nl67375sex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ggsb1rhb3nl67375sex.png" alt="sign_up_okta" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.okta.com/signup/" rel="noopener noreferrer"&gt;https://developer.okta.com/signup/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Okta with Work Email
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foymn37l7oexb5ihzicr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foymn37l7oexb5ihzicr4.png" alt="okta_verify" width="511" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Okta Verify for Mac OS locally
&lt;/h2&gt;

&lt;p&gt;Install Okta Verify from App Store.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhn939ewtxvjkk4apj6k7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhn939ewtxvjkk4apj6k7.png" alt="okta_verify_for_mac_install" width="549" height="344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Test: Login Okta
&lt;/h3&gt;

&lt;p&gt;Login completed!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7jxszslfz0o57g2virh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr7jxszslfz0o57g2virh.png" alt="okta_login_complete" width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Okta App Integration
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
Official Athenz Doc: &lt;a href="https://github.com/AthenZ/athenz/blob/master/docker/docs/IdP/Auth0.md" rel="noopener noreferrer"&gt;https://github.com/AthenZ/athenz/blob/master/docker/docs/IdP/Auth0.md&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel6xcdfj4a5vi7u261ua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel6xcdfj4a5vi7u261ua.png" alt="create_app_integration" width="800" height="370"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://integrator-8302118-admin.okta.com/admin/apps/active" rel="noopener noreferrer"&gt;https://integrator-8302118-admin.okta.com/admin/apps/active&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click next with &lt;code&gt;OIDC&lt;/code&gt; &amp;amp; &lt;code&gt;Web Application&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjn8bdinwc502s77vndt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmjn8bdinwc502s77vndt.png" alt="odic_n_web_app" width="800" height="704"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Proof of possession&lt;/code&gt;: Makes required signed token, for now we skip&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Setup: Okta Authorization Server API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5h7szouscq5u5ruwr0o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr5h7szouscq5u5ruwr0o.png" alt="authorization_server_api" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://integrator-8302118-admin.okta.com/admin/oauth2/as" rel="noopener noreferrer"&gt;https://integrator-8302118-admin.okta.com/admin/oauth2/as&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup: Okta Policy &amp;amp; Rules
&lt;/h2&gt;

&lt;p&gt;Create a scope &lt;code&gt;athenz&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1hiisle9l22otecv7nm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg1hiisle9l22otecv7nm.png" alt="create_scope" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Test: Token Preview
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs5c65cfcuwq396ekpqgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs5c65cfcuwq396ekpqgq.png" alt="get_token_preview" width="800" height="517"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup: &lt;code&gt;sub&lt;/code&gt; as athenz user service name
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/AthenZ/athenz/blob/master/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/oauth/OAuthCertBoundJwtAccessTokenAuthority.java#L100-L102" rel="noopener noreferrer"&gt;Source code&lt;/a&gt; for authorized client ids not mandatory&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/AthenZ/athenz/blob/master/libs/java/auth_core/src/main/java/com/yahoo/athenz/auth/oauth/OAuthCertBoundJwtAccessTokenAuthority.java#L246-L252" rel="noopener noreferrer"&gt;Source code&lt;/a&gt; for using &lt;code&gt;sub&lt;/code&gt; as service principal&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Athenz only sees the &lt;code&gt;sub&lt;/code&gt; field to define who you are. You can set up conversion field too but for now we can simply do this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpx8sn4q4o348ngygzsjs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpx8sn4q4o348ngygzsjs.png" alt="sub_as_user_sevice_name" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Test: &lt;code&gt;sub&lt;/code&gt; from Token Preview
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5obegdd2ibkiq378prdq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5obegdd2ibkiq378prdq.png" alt="test_sub_is_correct" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup: Access Token
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3eedfvu7h2qcwdkr8636.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3eedfvu7h2qcwdkr8636.png" alt="id_and_secret" width="800" height="633"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"0oaz36xyehsYf8Cwz697"&lt;/span&gt;
&lt;span class="nv"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"PUT_YOUR_SECRET_HERE"&lt;/span&gt;

curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/x-www-form-urlencoded"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLIENT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLIENT_SECRET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://integrator-8302118.okta.com/oauth2/default/v1/token"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"grant_type=client_credentials"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"scope=athenz"&lt;/span&gt; | jq

&lt;span class="c"&gt;# {&lt;/span&gt;
&lt;span class="c"&gt;#   "token_type": "Bearer",&lt;/span&gt;
&lt;span class="c"&gt;#   "expires_in": 3600,&lt;/span&gt;
&lt;span class="c"&gt;#   "access_token": "CENSORED",&lt;/span&gt;
&lt;span class="c"&gt;#   "scope": "athenz"&lt;/span&gt;
&lt;span class="c"&gt;# }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test: Access Token
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"PUT_YOUR_ACCESS_TOKEN"&lt;/span&gt;
curl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://localhost:4443/zms/v1/domain"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Adding ZMS properties so that ZMS can trust the okta verify
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflib5z7lsqmjj2dnmda7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflib5z7lsqmjj2dnmda7.png" alt="zms_properties_setting" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can omit &lt;code&gt;athenz.auth.oauth.jwt.authorized_client_ids_path&lt;/code&gt; because we setup the email address as the SSOT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;### Okta Configuration for ZMS ###&lt;/span&gt;

athenz.zms.authority_classes&lt;span class="o"&gt;=&lt;/span&gt;com.yahoo.athenz.auth.impl.CertificateAuthority,com.yahoo.athenz.auth.impl.AuthorizedServiceAuthHeaderAuthority,com.yahoo.athenz.auth.oauth.OAuthCertBoundJwtAccessTokenAuthority

&lt;span class="c"&gt;# Issuer:&lt;/span&gt;
athenz.auth.oauth.jwt.claim.aud&lt;span class="o"&gt;=&lt;/span&gt;api://default
athenz.auth.oauth.jwt.claim.iss&lt;span class="o"&gt;=&lt;/span&gt;https://integrator-8302118.okta.com/oauth2/default
athenz.auth.oauth.jwt.claim.scope&lt;span class="o"&gt;=&lt;/span&gt;athenz

athenz.auth.oauth.jwt.parser.jwks_url&lt;span class="o"&gt;=&lt;/span&gt;https://integrator-8302118.okta.com/oauth2/default/v1/keys
athenz.auth.oauth.jwt.auth0.claim_client_id&lt;span class="o"&gt;=&lt;/span&gt;cid

athenz.auth.oauth.jwt.verify_cert_thumbprint&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false
&lt;/span&gt;athenz.auth.oauth.jwt.cert.exclude_role_certificates&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zqujvfabmo0ipx8f0ll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zqujvfabmo0ipx8f0ll.png" alt="zms_setting" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Make sure to restart the ZMS server to get the changes!
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout restart deployment/athenz-zms-server &lt;span class="nt"&gt;-n&lt;/span&gt; athenz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Set k8s secret for proxy credentials
&lt;/h2&gt;

&lt;p&gt;Athenz UI does not have any mechanism to handle the Oauth (completely separated) so we want to build our own proxy so that all Athenz cares is about the token stored as Cookie,&lt;/p&gt;

&lt;p&gt;But before we do anything, we need the id and secret to represent the proxy so that proxy is trusted by Okta server. To do this correctly, we want to create a k8s secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Enter Okta Client ID: "&lt;/span&gt;
&lt;span class="nb"&gt;read &lt;/span&gt;_CLIENT_ID
&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"Enter Okta Client Secret: "&lt;/span&gt;
&lt;span class="nb"&gt;read &lt;/span&gt;_CLIENT_SECRET
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;

&lt;span class="nv"&gt;_COOKIE_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 32 | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="s1"&gt;'+/'&lt;/span&gt; &lt;span class="s1"&gt;'-_'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;_ns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"athenz"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🔐 Creating Kubernetes Secret [oauth2-proxy-creds] in ns [&lt;/span&gt;&lt;span class="nv"&gt;$_ns&lt;/span&gt;&lt;span class="s2"&gt;]..."&lt;/span&gt;
kubectl delete secret oauth2-proxy-creds &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="nt"&gt;--ignore-not-found&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
kubectl create secret generic oauth2-proxy-creds &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$_ns&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client-id&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_CLIENT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client-secret&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_CLIENT_SECRET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cookie-secret&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;_COOKIE_SECRET&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Create proxy container
&lt;/h2&gt;

&lt;p&gt;Copy the following container spec right under the &lt;code&gt;spec.template.spec.containers&lt;/code&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oauth2-proxy&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;quay.io/oauth2-proxy/oauth2-proxy:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3100&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auth-proxy&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OAUTH2_PROXY_CLIENT_ID&lt;/span&gt;
          &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oauth2-proxy-creds&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;client-id&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OAUTH2_PROXY_CLIENT_SECRET&lt;/span&gt;
          &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oauth2-proxy-creds&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;client-secret&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OAUTH2_PROXY_COOKIE_SECRET&lt;/span&gt;
          &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oauth2-proxy-creds&lt;/span&gt;
              &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cookie-secret&lt;/span&gt;
        &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# destination of this container:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--upstream=http://127.0.0.1:3000/&lt;/span&gt;
        &lt;span class="c1"&gt;# this container's port, the same as the .ports[0].containerPort:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--http-address=0.0.0.0:3100&lt;/span&gt;
        &lt;span class="c1"&gt;# Okta setting:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--provider=oidc&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--oidc-issuer-url=https://integrator-8302118.okta.com/oauth2/default&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--email-domain=*&lt;/span&gt;
        &lt;span class="c1"&gt;# "Athenz-Principal-Auth" is the default (modifiable) cookie name&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--cookie-name=Athenz-Principal-Auth&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--cookie-secure=false&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--pass-access-token=true&lt;/span&gt;
        &lt;span class="c1"&gt;# Let the UI read?&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--set-xauthrequest=true&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--pass-user-headers=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Remove &lt;code&gt;STATIC_USER_NAME&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Remove the following line from the &lt;code&gt;spec.template.spec.containers&lt;/code&gt; section, this is only for test and if you want to use the admin back, please do add them later once again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;STATIC_USER_NAME&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;athenz_admin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Modify the UI config
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
&lt;code&gt;docker/ui/conf/extended-config.js&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Modify &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;authUserEmailHeader&lt;/code&gt; to &lt;code&gt;X-Forwarded-Email&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; &lt;code&gt;authUserNameHeader&lt;/code&gt; to &lt;code&gt;X-Forwarded-Preferred-Username&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kwfygk2vhti982aw74i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8kwfygk2vhti982aw74i.png" alt="fixed_wrongful_not_standard_prefix_name" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify: Does it work?
&lt;/h2&gt;

&lt;p&gt;Please refer to the Result section above to see the verification steps and outcome.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I learned
&lt;/h1&gt;

&lt;p&gt;Not every tech dive ends with a perfectly working architecture, and that is completely fine.&lt;/p&gt;

&lt;p&gt;One of my core principles for maintaining a Weekly Dive habit is knowing how to manage failure gracefully. If I stubbornly pushed to resolve this Okta identity mapping issue, I would have compromised the preparation time for &lt;a href="https://community.linuxfoundation.org/events/details/lfhq-lf-ai-data-foundation-presents-japan-regional-user-group-rug-meet-up-1/" rel="noopener noreferrer"&gt;my upcoming LF AI &amp;amp; Data Japan RUG presentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;By defining this as a [Post-Mortem], I accomplished two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I left a clear, reproducible architecture footprint (oauth2-proxy + Okta OIDC) that I can easily pick back up later.&lt;/li&gt;
&lt;li&gt;I maintained my documentation consistency without feeling guilty about an "incomplete" project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What's next?
&lt;/h1&gt;

&lt;p&gt;For the next 4 weeks, my Weekly Dives will be entirely dedicated to &lt;a href="https://community.linuxfoundation.org/events/details/lfhq-lf-ai-data-foundation-presents-japan-regional-user-group-rug-meet-up-1/" rel="noopener noreferrer"&gt;an Epic: AI Data Japan RUG Prep&lt;/a&gt;. Sometimes the best engineering decision you can make is knowing when to pause a task, log the exact error state, and pivot to the higher-priority deadline.&lt;/p&gt;

&lt;h1&gt;
  
  
  Closing
&lt;/h1&gt;

&lt;p&gt;If you enjoyed this deep dive, please leave a like &amp;amp; subscribe for more!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjk2zj4uw0ssfwdxfz4p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffjk2zj4uw0ssfwdxfz4p.png" alt="cats_thumbs_up" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>okta</category>
      <category>athenz</category>
      <category>postmortem</category>
      <category>oauth2proxy</category>
    </item>
    <item>
      <title>I Deployed Athenz/k8s-athenz-syncer locally! (Feat. Fixing Legacy Code)</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Sun, 11 Jan 2026 19:38:38 +0000</pubDate>
      <link>https://forem.com/mlajkim/i-deployed-athenzk8s-athenz-syncer-locally-feat-fixing-legacy-code-2lcb</link>
      <guid>https://forem.com/mlajkim/i-deployed-athenzk8s-athenz-syncer-locally-feat-fixing-legacy-code-2lcb</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
In hurry? Jump directly to Result section to see the outcome of this dive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Athenz provides the following API endpoints for getting Athenz domain and its role/policy information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/v1/domain/{domainName}/group/admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/v1/domain/{domainName}/group/viewer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, if your applications are running in Kubernetes, constantly querying these endpoints can be inefficient. Ideally, you want a synchronization mechanism to cache this data within the cluster. Furthermore, you might want to decouple your applications from a direct dependency on Athenz to improve resilience.&lt;/p&gt;

&lt;p&gt;Let's be realistic—building a custom client to fetch, cache, and manage these resources within Kubernetes is a hassle. Why reinvent the wheel when you just want to consume the data?&lt;/p&gt;

&lt;p&gt;That's why I looked into &lt;a href="https://github.com/AthenZ/k8s-athenz-syncer" rel="noopener noreferrer"&gt;Athenz/k8s-athenz-syncer&lt;/a&gt;. It is an existing tool designed to sync Athenz data into Kubernetes Custom Resources (CRDs) called &lt;code&gt;AthenzDomain&lt;/code&gt;, effectively handling the heavy lifting for us. In this post, I’ll walk through how to deploy this syncer, fix a few build issues I encountered, and explore how it can save us from writing unnecessary boilerplate code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Goal&lt;/li&gt;
&lt;li&gt;Result&lt;/li&gt;
&lt;li&gt;
Setup

&lt;ul&gt;
&lt;li&gt;Setup: Working directory&lt;/li&gt;
&lt;li&gt;Setup: Athenz and Local Kubernetes Cluster&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;Setup: Set UI for web page&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;Setup: Clone k8s-athenz-syncer&lt;/li&gt;
&lt;li&gt;Setup: Build image&lt;/li&gt;
&lt;li&gt;Setup: Load image to kind cluster&lt;/li&gt;
&lt;li&gt;Setup: Deploy manifests&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;Setup: Create a secret to represent &lt;code&gt;k8s-athenz-syncer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Setup: Create our custom deployment&lt;/li&gt;
&lt;li&gt;Verify: Does it work?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;What I learned&lt;/li&gt;

&lt;li&gt;What's next?&lt;/li&gt;

&lt;li&gt;Dive Hours: 24.5 Hours&lt;/li&gt;

&lt;li&gt;Closing&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Result
&lt;/h1&gt;

&lt;p&gt;I successfully deployed &lt;code&gt;k8s-athenz-syncer&lt;/code&gt;, and the &lt;code&gt;AthenzDomain&lt;/code&gt; CRD was registered as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3lck2yx5rrpsc1lt8rr.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc3lck2yx5rrpsc1lt8rr.gif" alt="01" width="760" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, I created a domain named &lt;code&gt;home.syncer&lt;/code&gt; and a role &lt;code&gt;can-i-see-this-role-in-crd&lt;/code&gt; to see if syncer can really sync the AthenzDomain CRD:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gmaykzhog6derze4flx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3gmaykzhog6derze4flx.gif" alt="02" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, I created a Kubernetes namespace &lt;code&gt;home-syncer&lt;/code&gt; (Note: Athenz domains replace dots &lt;code&gt;.&lt;/code&gt; with dashes &lt;code&gt;-&lt;/code&gt; in K8s namespaces). I verified that the syncer successfully generated the &lt;code&gt;AthenzDomain&lt;/code&gt; resource, with its roles and policies correctly synced:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frystfko103bhz6amaici.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frystfko103bhz6amaici.gif" alt="03" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Setup: Working directory
&lt;/h2&gt;

&lt;p&gt;Let's set up the working directory. Feel free to use your own, but here is an idempotent script for a quick start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;test_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deploy_k8s_athenz_syncer
&lt;span class="nv"&gt;tmp_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%y%m%d_%H%M%S_&lt;span class="nv"&gt;$test_name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/test_dive/&lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/test_dive/&lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Athenz and Local Kubernetes Cluster
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!WARNING]&lt;br&gt;
The following script only works on macOS. Let me know in comments if you want to use other platforms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following script will set up a local Kubernetes cluster and install the Athenz server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mlajkim/dive-manifest.git manifest
make &lt;span class="nt"&gt;-C&lt;/span&gt; manifest setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test
&lt;/h3&gt;

&lt;p&gt;Let's verify that the Athenz server is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; athenz

&lt;span class="c"&gt;## Setup: Set UI for web page&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[!&lt;/span&gt;TIP]
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; - To forward: &lt;span class="sb"&gt;`&lt;/span&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; athenz athenz-ui deployment/athenz-ui 3000:3000 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; - To &lt;span class="nb"&gt;kill &lt;/span&gt;later: &lt;span class="sb"&gt;`&lt;/span&gt;pkill &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"athenz-ui"&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;


&lt;span class="c"&gt;### Test&lt;/span&gt;

Open the UI &lt;span class="k"&gt;in &lt;/span&gt;browser:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sh&lt;br&gt;
open &lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Setup: Clone k8s-athenz-syncer

&amp;gt; [!NOTE]
&amp;gt; Once the PR https://github.com/AthenZ/k8s-athenz-syncer/pull/45 is released, we will use `mlajkim`'s fork.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sh&lt;br&gt;
git clone -b fix/deprecated-Dockerfile-images-and-CRD-definition-API &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer.git" rel="noopener noreferrer"&gt;https://github.com/mlajkim/k8s-athenz-syncer.git&lt;/a&gt; syncer&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Setup: Build image

Let's build the image locally:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sh&lt;br&gt;
(cd syncer &amp;amp;&amp;amp; docker build -t local/k8s-athenz-syncer:latest .)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Setup: Load image to kind cluster

Since we are using Kind, we need to load the locally built `k8s-athenz-syncer` image into the cluster:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sh&lt;br&gt;
kind load docker-image local/k8s-athenz-syncer:latest&lt;/p&gt;
&lt;h1&gt;
  
  
  Image: "local/k8s-athenz-syncer:latest" with ID "sha256:bb5bcf2d9c362a46444f9476791f6c9e3f81ce6abf6ebc07d3228b9b7da53fa8" not yet present on node "kind-control-plane", loading...
&lt;/h1&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

## Setup: Deploy manifests

&amp;gt; [!TIP]
&amp;gt; For the detailed explanation of each command, please refer to the [following](https://github.com/mlajkim/k8s-athenz-syncer/tree/fix/deprecated-Dockerfile-images-and-CRD-definition-API?tab=readme-ov-file#install)

&amp;gt; [!WARNING]
&amp;gt; We will create a custom `deployment.yaml` later, as the sample provided in the OSS repository requires configurations that are a bit complex for a quick demo.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;&lt;br&gt;
sh&lt;br&gt;
kubectl create ns kube-yahoo&lt;br&gt;
kubectl apply -f ./syncer/k8s/athenzdomain.yaml&lt;br&gt;
kubectl apply -f ./syncer/k8s/serviceaccount.yaml&lt;br&gt;
kubectl apply -f ./syncer/k8s/clusterrole.yaml&lt;br&gt;
kubectl apply -f ./syncer/k8s/clusterrolebinding.yaml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
### Test

&amp;gt; [!TIP]
&amp;gt; It is okay to see `No resources found` at this stage, as the syncer managing the `AthenzDomain` is not yet deployed.

The commands above registered the `AthenzDomain CRD` (shortened to `domain`). Let's quickly check:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sh&lt;br&gt;
kubectl get domain&lt;/p&gt;

&lt;h1&gt;
  
  
  No resources found
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Setup: Create a secret to represent `k8s-athenz-syncer`

Unlike a standard production setup which uses [CopperArgos](https://github.com/AthenZ/athenz/blob/master/docs/copper_argos.md) to auto-distribute X.509 certificates, we will simply use the root certificate for this quick demo to represent `k8s-athenz-syncer` as an Athenz service.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sh&lt;br&gt;
kubectl create secret generic k8s-athenz-syncer-cert \&lt;br&gt;
  -n kube-yahoo \&lt;br&gt;
  --from-file=cert.pem=./athenz/certs/athenz_admin.cert.pem \&lt;br&gt;
  --from-file=key.pem=./athenz/keys/athenz_admin.private.pem \&lt;br&gt;
  --from-file=ca.pem=./athenz/certs/ca.cert.pem&lt;/p&gt;

&lt;h1&gt;
  
  
  secret/k8s-athenz-syncer-cert created
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
## Setup: Create our custom deployment

&amp;gt; [!NOTE]
&amp;gt; Note the following configurations:
&amp;gt; - We set `zms-url=https://athenz-zms-server.athenz:4443/zms/v1` because we are sharing the same Kubernetes cluster with the Athenz server
&amp;gt; - We set `update-cron=5s` to see the synchronization results quickly

As mentioned earlier, we are using a custom manifest that mounts the Secret we just created:

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
sh&lt;br&gt;
cat &amp;lt;&amp;lt;EOF | kubectl apply -f -&lt;br&gt;
apiVersion: apps/v1&lt;br&gt;
kind: Deployment&lt;br&gt;
metadata:&lt;br&gt;
  name: k8s-athenz-syncer&lt;br&gt;
  namespace: kube-yahoo&lt;br&gt;
  labels:&lt;br&gt;
    app: k8s-athenz-syncer&lt;br&gt;
spec:&lt;br&gt;
  replicas: 1&lt;br&gt;
  selector:&lt;br&gt;
    matchLabels:&lt;br&gt;
      app: k8s-athenz-syncer&lt;br&gt;
  strategy:&lt;br&gt;
    rollingUpdate:&lt;br&gt;
      maxSurge: 50%&lt;br&gt;
      maxUnavailable: 0%&lt;br&gt;
    type: RollingUpdate&lt;br&gt;
  template:&lt;br&gt;
    metadata:&lt;br&gt;
      labels:&lt;br&gt;
        app: k8s-athenz-syncer&lt;br&gt;
    spec:&lt;br&gt;
      containers:&lt;br&gt;
      - name: syncer&lt;br&gt;
        image: local/k8s-athenz-syncer&lt;br&gt;
        imagePullPolicy: IfNotPresent&lt;br&gt;
        resources:&lt;br&gt;
          limits:&lt;br&gt;
            cpu: 1&lt;br&gt;
            memory: 1Gi&lt;br&gt;
          requests:&lt;br&gt;
            cpu: 1&lt;br&gt;
            memory: 1Gi&lt;br&gt;
        args:&lt;br&gt;
        - --zms-url=&lt;a href="https://athenz-zms-server.athenz:4443/zms/v1" rel="noopener noreferrer"&gt;https://athenz-zms-server.athenz:4443/zms/v1&lt;/a&gt;&lt;br&gt;
        - --update-cron=5s&lt;br&gt;
        - --cert=/var/run/athenz/cert.pem&lt;br&gt;
        - --key=/var/run/athenz/key.pem&lt;br&gt;
        - --cacert=/var/run/athenz/ca.pem&lt;br&gt;
        - --exclude-namespaces=kube-system,kube-public,kube-k8s-athenz-syncer,default,local-path-storage,kube-node-lease,athenz,ajktown-api,kube-yahoo&lt;br&gt;
        volumeMounts:&lt;br&gt;
        - name: athenz-certs&lt;br&gt;
          mountPath: /var/run/athenz&lt;br&gt;
          readOnly: true&lt;br&gt;
      serviceAccountName: k8s-athenz-syncer&lt;br&gt;
      volumes:&lt;br&gt;
      - name: athenz-certs&lt;br&gt;
        secret:&lt;br&gt;
          secretName: k8s-athenz-syncer-cert&lt;br&gt;
EOF&lt;/p&gt;

&lt;h1&gt;
  
  
  deployment.apps/k8s-athenz-syncer created
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


## Verify: Does it work?

Please refer to the [Result](#result) section above to see the verification steps and outcome.

# What I learned

Here's what I've learned:

- I discovered that when members change in a Trusted Domain (delegated role), the Modified Date and ETag of the Provider Domain do NOT change.
- I learned that `CustomResourceDefinition`'s `v1beta1` API has been deprecated
- I learned that you can enforce type in CRD

# What's next?

I will dive into jag token and how Athenz integrates with it, and what Athenz lacks so far.

# Dive Hours: 24.5 Hours

&amp;gt; [!NOTE]
&amp;gt; `aegis` that utilizes syncer's CRD and kubernetes RBAC enforcer has been stopped as they do not sync super well yet.

- `1/1 Thu`: 6.75 Hours
- `1/2 Fri`: 4.75 Hours
- `1/3 Sat`: 6.5 Hours
- `1/11 Sun`: 6.5 Hours


With the separate PRs of the following:

- https://github.com/mlajkim/aegis/pull/1
- https://github.com/mlajkim/dive-manifest/pull/1
- https://github.com/mlajkim/dive-manifest/pull/2
- https://github.com/mlajkim/dive-manifest/pull/3
- https://github.com/AthenZ/k8s-athenz-syncer/pull/45
- https://github.com/AthenZ/athenz/pull/3166

# Closing

If you enjoyed this deep dive, please leave a like &amp;amp; subscribe for more!

![cats_thumbs_up](https://raw.githubusercontent.com/mlajkim/dive-deep/main/weekly_dives/athenz/week_01_2026/assets/cats_thumbs_up.png)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>athenz</category>
      <category>kubernetes</category>
      <category>k8sathenzsyncer</category>
    </item>
    <item>
      <title>Integrate Athenz Notification Feature with AWS SES</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Wed, 07 Jan 2026 08:41:13 +0000</pubDate>
      <link>https://forem.com/mlajkim/integrate-athenz-notification-feature-with-aws-ses-2471</link>
      <guid>https://forem.com/mlajkim/integrate-athenz-notification-feature-with-aws-ses-2471</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
In hurry? Jump directly to Result section to see the outcome of this dive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I’ve been diving into &lt;a href="https://github.com/AthenZ/athenz" rel="noopener noreferrer"&gt;Athenz&lt;/a&gt;, an open-source RBAC/ABAC platform, running it on a local Kubernetes (&lt;a href="https://kind.sigs.k8s.io/" rel="noopener noreferrer"&gt;Kind&lt;/a&gt;) cluster. Everything was working great until I needed to test the approval workflow.&lt;/p&gt;

&lt;p&gt;I looked through official documentations and found out &lt;a href="https://athenz.github.io/athenz/email_notifications/" rel="noopener noreferrer"&gt;this "Email Notifications - Athenz"&lt;/a&gt;, and they tell me that you can simply utilize already built AWS SES integration if you &lt;em&gt;only&lt;/em&gt; run your Athenz server on AWS infrastructure, but I was running it locally. So, I had to figure out how to make it work on my own.&lt;/p&gt;

&lt;p&gt;The official doc also mentioned as the following that you can build your own notification plugin, so I decided to give it a try:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This requires Athenz to be deployed on AWS. Users may use other Email Providers by following the steps to Enable &lt;a href="https://athenz.github.io/athenz/email_notifications/#enable-notifications-using-other-providers" rel="noopener noreferrer"&gt;Notifications using other Providers&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  General Architecture
&lt;/h1&gt;

&lt;p&gt;This is the general architecture of how the &lt;code&gt;Athenz Custom Plugin&lt;/code&gt; works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fui5z8ozkdjabcxljmf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fui5z8ozkdjabcxljmf.png" alt="plugin_n_aws_sms_architecture" width="800" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Table of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Goal&lt;/li&gt;
&lt;li&gt;General Architecture&lt;/li&gt;
&lt;li&gt;Table of Contents&lt;/li&gt;
&lt;li&gt;Result&lt;/li&gt;
&lt;li&gt;
Setup

&lt;ul&gt;
&lt;li&gt;Setup: Working directory&lt;/li&gt;
&lt;li&gt;Setup: Athenz and Local Kubernetes Cluster&lt;/li&gt;
&lt;li&gt;Setup: Clone Athenz Plugin&lt;/li&gt;
&lt;li&gt;Setup: AWS SES Recipient Setup&lt;/li&gt;
&lt;li&gt;Setup: Open AWS SES Console&lt;/li&gt;
&lt;li&gt;Setup: Get AWS SES SMTP Credentials&lt;/li&gt;
&lt;li&gt;Setup: Get Smtp credentials&lt;/li&gt;
&lt;li&gt;Setup: Create secret for AWS SES&lt;/li&gt;
&lt;li&gt;Setup: Build jar and deploy plugin as configmap in Kubernetes&lt;/li&gt;
&lt;li&gt;Setup: Modify Athenz ZMS Server Deployment to use the Plugin and Secret&lt;/li&gt;
&lt;li&gt;Verify: Does it work?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

What I learned

&lt;ul&gt;
&lt;li&gt;What's Next?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Dive Hours: 9.25h&lt;/li&gt;

&lt;li&gt;Closing&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Result
&lt;/h1&gt;

&lt;p&gt;I have successfully built and deployed a custom Athenz notification plugin that integrates with &lt;strong&gt;AWS SES&lt;/strong&gt;. The plugin monitors specific Athenz events—such as role membership changes or domain modifications—and triggers email notifications via AWS SES.&lt;/p&gt;

&lt;p&gt;The following GIF demonstrates the end-to-end workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Filter Logs&lt;/strong&gt;: Search for &lt;code&gt;AWS&lt;/code&gt; to isolate logs related to the &lt;strong&gt;AWS SES Plugin&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configure Recipient&lt;/strong&gt;: Add a recipient by defining a &lt;code&gt;user.&amp;lt;identifier&amp;gt;&lt;/code&gt; rule (e.g., adding &lt;code&gt;user.jkim67cloud&lt;/code&gt; maps to &lt;code&gt;jkim67cloud@gmail.com&lt;/code&gt;). (Only supports &lt;code&gt;gmail.com&lt;/code&gt; for now)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Role&lt;/strong&gt;: Create a new role that requires an &lt;strong&gt;approval review&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request Membership&lt;/strong&gt;: Attempt to add &lt;code&gt;user.test&lt;/code&gt; as a member to the role.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trigger Notification&lt;/strong&gt;: The ZMS Server detects the pending request and triggers a notification to the role administrators.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify Email&lt;/strong&gt;: Check the inbox to confirm the receipt of the notification email sent via AWS SES.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzd1h33riuekxzt68pi8h.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzd1h33riuekxzt68pi8h.gif" alt="result" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Setup: Working directory
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;test_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;email_notification_plugin
&lt;span class="nv"&gt;tmp_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%y%m%d_%H%M%S_&lt;span class="nv"&gt;$test_name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/test_dive/&lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/test_dive/&lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: Athenz and Local Kubernetes Cluster
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
Soon we are coming with the local cluster + Athenz server setup guide! Meanwhile, please refer to the following guides&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Local k8s server setup guide: &lt;a href="https://dev.to/mlajkim/stop-using-magic-building-a-kubernetes-operator-from-scratch-mo2#a-local-kubernetes-cluster-kind"&gt;https://dev.to/mlajkim/stop-using-magic-building-a-kubernetes-operator-from-scratch-mo2#a-local-kubernetes-cluster-kind&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Local athenz server setup guide: &lt;a href="https://dev.to/mlajkim/stop-using-magic-building-a-kubernetes-operator-from-scratch-mo2#b-deploy-athenz-server"&gt;https://dev.to/mlajkim/stop-using-magic-building-a-kubernetes-operator-from-scratch-mo2#b-deploy-athenz-server&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setup: Clone Athenz Plugin
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/mlajkim/athenz-amazon-ses-notification-plugin.git plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup: AWS SES Recipient Setup
&lt;/h2&gt;

&lt;p&gt;First, we need to set up &lt;strong&gt;trusted&lt;/strong&gt; email addresses in AWS SES. AWS restricts sending emails to unverified addresses to prevent them from being flagged as spam. Therefore, we must first verify the email addresses we intend to use. For this example, we will simply use our personal email address:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf90f30ev0yamt8fsoye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf90f30ev0yamt8fsoye.png" alt="setup_allowed_email" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup: Open AWS SES Console
&lt;/h3&gt;

&lt;p&gt;Open &lt;a href="https://ap-northeast-1.console.aws.amazon.com/ses/home?region=ap-northeast-1#/identities" rel="noopener noreferrer"&gt;Amazon SES Console's identity management page&lt;/a&gt;, and hit the &lt;code&gt;Create identity&lt;/code&gt; button:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwe4de0t6lglpcsi9doyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwe4de0t6lglpcsi9doyn.png" alt="aws_ses_create_identity" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select &lt;code&gt;Email address&lt;/code&gt; as identity type, and input your personal email address that you want to use it as the recipient of Athenz notification emails:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmucxwpepelhdzunirvs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhmucxwpepelhdzunirvs.png" alt="create_identity" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will shortly receive a verification email from AWS SES. Open the email and click the &lt;code&gt;Verify email address&lt;/code&gt; button to complete the verification process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17ojm8pbdedc7qaf05ac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F17ojm8pbdedc7qaf05ac.png" alt="verify_email" width="728" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Get AWS SES SMTP Credentials
&lt;/h2&gt;

&lt;p&gt;For Athenz Server to connect to the public AWS SES service, we need to create SMTP credentials that Athenz server will use to authenticate itself when sending emails via AWS SES:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcofxrlzwjxc4ihnsnzfo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcofxrlzwjxc4ihnsnzfo.png" alt="set_smtp_credentials_architecture" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup: Get Smtp credentials
&lt;/h3&gt;

&lt;p&gt;Click &lt;code&gt;Create SMTP Credentials&lt;/code&gt; button on the &lt;a href="https://ap-northeast-1.console.aws.amazon.com/ses/home?region=ap-northeast-1#/smtp" rel="noopener noreferrer"&gt;SMTP Settings page&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4jkvbtd4pfs6pez7zqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm4jkvbtd4pfs6pez7zqs.png" alt="set_smtp_credentials" width="800" height="326"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Store the generated username and password somewhere safe, as we will need them later when creating Kubernetes secret:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1l1diquntl8p8lt36zyw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1l1diquntl8p8lt36zyw.png" alt="username_and_password" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Create secret for AWS SES
&lt;/h2&gt;

&lt;p&gt;With the &lt;code&gt;STMP Username&lt;/code&gt; and &lt;code&gt;SMTP Password&lt;/code&gt; we just created, we can now create a Kubernetes secret that will store these credentials securely. The plugin repo contains a Makefile target that automates this process for us. Simply run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nt"&gt;-C&lt;/span&gt; plugin create-aws-ses-secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxotdkbyo8my1f7hl32wt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxotdkbyo8my1f7hl32wt.png" alt="create_aws_ses_secret_result" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Build jar and deploy plugin as configmap in Kubernetes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjmm1j6aw0z7376ltb56.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjmm1j6aw0z7376ltb56.png" alt="build_plugin" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will build the plugin (as RedBox) jar file and deploy it as a configmap in our local Kubernetes cluster. The plugin repo contains a Makefile target that automates this process for us. Simply run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nt"&gt;-C&lt;/span&gt; plugin deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2za16chxxjrszv2uta3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2za16chxxjrszv2uta3.png" alt="make_deploy_result" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: Modify Athenz ZMS Server Deployment to use the Plugin and Secret
&lt;/h2&gt;

&lt;p&gt;We have the following ready so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS SES SMTP Credentials stored as Kubernetes Secret&lt;/li&gt;
&lt;li&gt;Athenz Notification Plugin stored as Kubernetes ConfigMap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we need to let ZMS Server know about these resources by modifying its deployment manifest. The plugin repo contains a Makefile target that automates this process for us. Simply run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nt"&gt;-C&lt;/span&gt; plugin patch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu94s3290v3inf48unaub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu94s3290v3inf48unaub.png" alt="alt patch_zms_deployment" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify: Does it work?
&lt;/h2&gt;

&lt;p&gt;Please refer to the Result section above to see the verification steps and outcome.&lt;/p&gt;

&lt;h1&gt;
  
  
  What I learned
&lt;/h1&gt;

&lt;p&gt;Through this project, I gained hands-on experience in extending a complex open-source platform and integrating it with cloud infrastructure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Plugin Injection &amp;amp; Classpath&lt;/strong&gt;: I discovered that Athenz supports custom plugin injection via the &lt;code&gt;USER_CLASSPATH&lt;/code&gt; environment defined path variable. This allows for seamless integration of custom logic into a running ZMS server without modifying the core codebase&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS SES Integration&lt;/strong&gt;: Successfully implemented a real-world email notification workflow using AWS SES, gaining a deeper understanding of SMTP authentication and identity verification&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced Developer UX&lt;/strong&gt;: I focused on improving the CLI experience by adding color-coded outputs and streamlined Makefile targets, making the deployment process more intuitive&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scripting &amp;amp; Database Exploration&lt;/strong&gt;: Refined my automation skills by improving &lt;code&gt;hack&lt;/code&gt; scripts and performed direct hands-on database queries within the Athenz DB to verify internal data states.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Documentation&lt;/strong&gt;: Utilized &lt;a href="https://excalidraw.com" rel="noopener noreferrer"&gt;Excalidraw&lt;/a&gt; to create technical diagrams, recognizing that visual aids significantly improve the readability and accessibility of complex architectures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;As I reflect on this dive, I plan to create a robust, "one-click" Makefile that orchestrates the entire Kubernetes cluster and Athenz server setup.&lt;/p&gt;

&lt;h1&gt;
  
  
  Dive Hours: 9.25h
&lt;/h1&gt;

&lt;p&gt;This post took me approximately 9.25 hours of focused work and development, broken down as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1/5/26&lt;/code&gt;: 5.25h&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1/6/26&lt;/code&gt;: 4h&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the separate PRs of the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/mlajkim/athenz-amazon-ses-notification-plugin/pull/1" rel="noopener noreferrer"&gt;Feat: plugin that works with Athenz v1.12.31 #1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mlajkim/athenz-amazon-ses-notification-plugin/pull/2" rel="noopener noreferrer"&gt;Feat: automated makefile for easier setup #2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Closing
&lt;/h1&gt;

&lt;p&gt;If you enjoyed this deep dive, please leave a like &amp;amp; subscribe for more!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffvd136e7n7gkymlclonw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffvd136e7n7gkymlclonw.png" alt="like_this_photo_cat" width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>athenz</category>
      <category>aws</category>
      <category>simpleemailservice</category>
      <category>plugin</category>
    </item>
    <item>
      <title>Stop Using Magic: Building a Kubernetes Operator from Scratch</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Tue, 30 Dec 2025 09:55:38 +0000</pubDate>
      <link>https://forem.com/mlajkim/stop-using-magic-building-a-kubernetes-operator-from-scratch-mo2</link>
      <guid>https://forem.com/mlajkim/stop-using-magic-building-a-kubernetes-operator-from-scratch-mo2</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
In hurry? Jump directly to Result section to see the outcome of this dive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello everyone! This post's primary goal is to demystify how Kubernetes Controllers work by building a custom &lt;a href="https://github.com/AthenZ/k8s-athenz-syncer" rel="noopener noreferrer"&gt;Athenz/k8s-athenz-syncer&lt;/a&gt; from scratch! Instead of relying on "magic" libraries or copying existing production code, we are diving deep into the core concepts by implementing the operator ourselves.&lt;/p&gt;

&lt;p&gt;By doing this, we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand the core concepts of Kubernetes Operators and the Reconciliation Loop.&lt;/li&gt;
&lt;li&gt;Learn how to interact with the Athenz ZMS server via its API programmatically.&lt;/li&gt;
&lt;li&gt;Implement a logic that automatically maps external identity roles (Athenz) to Kubernetes RBAC.&lt;/li&gt;
&lt;li&gt;Identify the subtle details often overlooked when simply deploying pre-made operators.&lt;/li&gt;
&lt;li&gt;Know exactly what to look for to catch the subtle details that other reviewers would miss&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Table of Contents
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Goal&lt;/li&gt;
&lt;li&gt;Table of Contents&lt;/li&gt;
&lt;li&gt;Result&lt;/li&gt;
&lt;li&gt;What I Learned&lt;/li&gt;
&lt;li&gt;
Walkthrough

&lt;ul&gt;
&lt;li&gt;Prerequisites &amp;amp; Setup&lt;/li&gt;
&lt;li&gt;a. Local Kubernetes Cluster (Kind)&lt;/li&gt;
&lt;li&gt;b. Deploy Athenz Server&lt;/li&gt;
&lt;li&gt;c. Configure Athenz Domains&lt;/li&gt;
&lt;li&gt;Implementation: The Hard &amp;amp; Clean Way&lt;/li&gt;
&lt;li&gt;1. Initialize the Project&lt;/li&gt;
&lt;li&gt;2. Make an operator that works from scratch&lt;/li&gt;
&lt;li&gt;3. Refactor the operator to make it cleaner&lt;/li&gt;
&lt;li&gt;4. Write demo/local setup guide in README.md&lt;/li&gt;
&lt;li&gt;5. Make &lt;code&gt;make run&lt;/code&gt; writes a config&lt;/li&gt;
&lt;li&gt;Verification: Does it actually work?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

What's next?

&lt;ul&gt;
&lt;li&gt;I: The "Weekly Dive": Performance Optimization&lt;/li&gt;
&lt;li&gt;II: Dissecting the Production Code (athenz/k8s-athenz-syncer)&lt;/li&gt;
&lt;li&gt;III: Contributing Back&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Dive Hours: 28 hours&lt;/li&gt;

&lt;li&gt;Closing&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Result
&lt;/h1&gt;

&lt;p&gt;I successfully built a working Kubernetes operator and you can find it here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to setup guide: &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-clean-way/blob/main/README.md#how-to-run-locally" rel="noopener noreferrer"&gt;mlajkim/k8s-athenz-syncer-the-hard-clean-way/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PR: &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-clean-way/pull/1" rel="noopener noreferrer"&gt;PR: k8s-athenz-syncer-the-hard-clean-way&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The operator, &lt;code&gt;k8s-athenz-syncer-the-hard-clean-way&lt;/code&gt;, performs the following actions automatically when a &lt;strong&gt;Namespace&lt;/strong&gt;  &lt;code&gt;&amp;lt;ns&amp;gt;&lt;/code&gt; is created in the cluster:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a corresponding &lt;strong&gt;Athenz Domain&lt;/strong&gt; (e.g., &lt;code&gt;eks.users.&amp;lt;ns&amp;gt;&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Creates &lt;strong&gt;Athenz Roles&lt;/strong&gt; within that domain based on a configuration.&lt;/li&gt;
&lt;li&gt;Creates &lt;strong&gt;Kubernetes RBAC Roles&lt;/strong&gt; that map to those Athenz roles.&lt;/li&gt;
&lt;li&gt;Periodically polls Athenz to sync member changes into Kubernetes RoleBindings.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Demo 1: Auto-creation of Resources in Athenz with k8s namespace only&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mlajkim/dive-deep/blob/main/weekly_dives/athenz/week_51_2025/assets/01_create_ns.gif" rel="noopener noreferrer"&gt;https://github.com/mlajkim/dive-deep/blob/main/weekly_dives/athenz/week_51_2025/assets/01_create_ns.gif&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmlajkim%2Fdive-deep%2Fmain%2Fweekly_dives%2Fathenz%2Fweek_51_2025%2Fassets%2F01_create_ns.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmlajkim%2Fdive-deep%2Fmain%2Fweekly_dives%2Fathenz%2Fweek_51_2025%2Fassets%2F01_create_ns.gif" alt="Demo" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo 2: Giving permission with Syncing Membership (Polling)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Operator &lt;code&gt;k8s-athenz-syncer-the-hard-clean-way&lt;/code&gt; periodically polls Athenz roles under certain parent domain (e.g., &lt;code&gt;eks.users&lt;/code&gt;), and syncs the members of the Athenz roles into corresponding Kubernetes RBAC Roles, which results in automatic access control based on Athenz role membership.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mlajkim/dive-deep/blob/main/weekly_dives/athenz/week_51_2025/assets/02_polling_athenz_roles.gif" rel="noopener noreferrer"&gt;https://github.com/mlajkim/dive-deep/blob/main/weekly_dives/athenz/week_51_2025/assets/02_polling_athenz_roles.gif&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmlajkim%2Fdive-deep%2Fmain%2Fweekly_dives%2Fathenz%2Fweek_51_2025%2Fassets%2F02_polling_athenz_roles.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmlajkim%2Fdive-deep%2Fmain%2Fweekly_dives%2Fathenz%2Fweek_51_2025%2Fassets%2F02_polling_athenz_roles.gif" alt="Demo" width="" height=""&gt;&lt;/a&gt;&lt;br&gt;
Operator &lt;code&gt;k8s-athenz-syncer-the-hard-clean-way&lt;/code&gt; makes sure that if you delete members from Athenz roles, the members are also removed from corresponding Kubernetes RBAC Roles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Demo 3: Restricting permission with Syncing Membership (Polling)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mlajkim/dive-deep/blob/main/weekly_dives/athenz/week_51_2025/assets/03_remove_athenz_role_members.gif" rel="noopener noreferrer"&gt;https://github.com/mlajkim/dive-deep/blob/main/weekly_dives/athenz/week_51_2025/assets/03_remove_athenz_role_members.gif&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmlajkim%2Fdive-deep%2Fmain%2Fweekly_dives%2Fathenz%2Fweek_51_2025%2Fassets%2F03_remove_athenz_role_members.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmlajkim%2Fdive-deep%2Fmain%2Fweekly_dives%2Fathenz%2Fweek_51_2025%2Fassets%2F03_remove_athenz_role_members.gif" alt="Demo" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  What I Learned
&lt;/h1&gt;

&lt;p&gt;Through this "hard way" implementation, I gained several key technical insights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Power of Kubebuilder Scaffolding&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Starting from scratch doesn't mean writing boilerplate. &lt;code&gt;kubebuilder&lt;/code&gt; abstracts away the complexity of leader election, metrics server, and signal handling, allowing us to focus purely on the &lt;code&gt;Reconcile&lt;/code&gt; logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Level-Triggered vs. Edge-Triggered&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubernetes controllers are primarily level-triggered. While I implemented a polling mechanism for the external Athenz API, the internal Kubernetes state relies on the &lt;code&gt;Reconcile&lt;/code&gt; loop constantly attempting to move the current state to the desired state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I learned that for external resources (like Athenz), we explicitly need to manage the polling interval or set up webhooks, as Kubernetes cannot "watch" an external API by default.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security Integration Details&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connecting Kubernetes RBAC with an external system isn't just about mapping strings. It involves handling X.509 certificates for authentication (Athenz) and correctly signing Kubernetes CSRs for user testing (&lt;code&gt;user.mlajkim&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Walkthrough
&lt;/h1&gt;

&lt;p&gt;Here is the step-by-step record of how I achieved the result.&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites &amp;amp; Setup
&lt;/h2&gt;
&lt;h3&gt;
  
  
  a. Local Kubernetes Cluster (Kind)
&lt;/h3&gt;

&lt;p&gt;I used &lt;code&gt;kind&lt;/code&gt; to run a cluster locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kind create cluster
kubectl cluster-info &lt;span class="nt"&gt;--context&lt;/span&gt; kind-kind

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  b. Deploy Athenz Server
&lt;/h3&gt;

&lt;p&gt;I utilized &lt;a href="https://github.com/ctyano" rel="noopener noreferrer"&gt;&lt;code&gt;@ctyano&lt;/code&gt;&lt;/a&gt;'s &lt;a href="https://github.com/ctyano/athenz-distribution" rel="noopener noreferrer"&gt;&lt;code&gt;athenz-distribution&lt;/code&gt;&lt;/a&gt; to deploy a local Athenz instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone and deploy Athenz&lt;/span&gt;
git clone https://github.com/ctyano/athenz-distribution.git athenz_distribution
make &lt;span class="nt"&gt;-C&lt;/span&gt; ./athenz_distribution clean-kubernetes-athenz deploy-kubernetes-athenz

&lt;span class="c"&gt;# Port forward the UI&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; athenz port-forward deployment/athenz-ui 3000:3000 &amp;amp;
open http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  c. Configure Athenz Domains
&lt;/h3&gt;

&lt;p&gt;Set up the ZMS server access and create the Top Level Domain (TLD) for testing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Port forward ZMS&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; athenz port-forward deployment/athenz-zms-server 4443:4443 &amp;amp;

&lt;span class="c"&gt;# Create TLD "eks"&lt;/span&gt;
curl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://localhost:4443/zms/v1/domain"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cert&lt;/span&gt; ./athenz_distribution/certs/athenz_admin.cert.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; ./athenz_distribution/keys/athenz_admin.private.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "eks", "adminUsers": ["user.athenz_admin"]}'&lt;/span&gt;

&lt;span class="c"&gt;# Create Subdomain "eks.users"&lt;/span&gt;
curl &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://localhost:4443/zms/v1/subdomain/eks"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cert&lt;/span&gt; ./athenz_distribution/certs/athenz_admin.cert.pem &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--key&lt;/span&gt; ./athenz_distribution/keys/athenz_admin.private.pem &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"parent": "eks", "name": "users", "adminUsers": ["user.athenz_admin"]}'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementation: The Hard &amp;amp; Clean Way
&lt;/h2&gt;

&lt;p&gt;Here is how I built the operator from scratch in a clean way.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Initialize the Project
&lt;/h3&gt;

&lt;p&gt;I initialized the project using &lt;code&gt;kubebuilder&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;repo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"github.com/mlajkim"&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; k8s-athenz-syncer-the-hard-clean-way &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;k8s-athenz-syncer-the-hard-clean-way
kubebuilder init &lt;span class="nt"&gt;--domain&lt;/span&gt; &lt;span class="s2"&gt;"ajktown.com"&lt;/span&gt; &lt;span class="nt"&gt;--repo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$repo&lt;/span&gt;&lt;span class="s2"&gt;/k8s-athenz-syncer-the-hard-clean-way"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Make an operator that works from scratch
&lt;/h3&gt;

&lt;p&gt;I first created an operator that works in bare minimum and deployed it public to the repository: &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-way/" rel="noopener noreferrer"&gt;k8s-athenz-syncer-the-hard-way&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Refactor the operator to make it cleaner
&lt;/h3&gt;

&lt;p&gt;But I found myself to improve the code structure and make it cleaner. So I created a new repository: &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-clean-way/" rel="noopener noreferrer"&gt;k8s-athenz-syncer-the-hard-clean-way&lt;/a&gt;, with &lt;code&gt;clean&lt;/code&gt; meaning that I re-organized the code structure to make it more modular and readable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-clean-way/pull/1" rel="noopener noreferrer"&gt;This is amount of changes I made in a PR&lt;/a&gt; to make it clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Write demo/local setup guide in README.md
&lt;/h3&gt;

&lt;p&gt;I realized that visuals speak louder than words when demonstrating infrastructure tools. Instead of greeting users with a wall of text, I structured the &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-clean-way/blob/main/README.md" rel="noopener noreferrer"&gt;README.md&lt;/a&gt; to lead with &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-clean-way/tree/main/assets" rel="noopener noreferrer"&gt;GIFs&lt;/a&gt; that showcase the operator's features immediately. Once I've captured the reader's interest, I provide a &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-clean-way/blob/main/README.md#how-to-run-locally" rel="noopener noreferrer"&gt;"copy-paste friendly" local setup guide&lt;/a&gt; to make the onboarding process as seamless as possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Make &lt;code&gt;make run&lt;/code&gt; writes a config
&lt;/h3&gt;

&lt;p&gt;To make the README.md easier to follow, I made &lt;code&gt;make run&lt;/code&gt; write a config file &lt;code&gt;config.yaml&lt;/code&gt; automatically with &lt;a href="https://github.com/mlajkim/k8s-athenz-syncer-the-hard-clean-way/blob/main/hack/ensure-config.sh" rel="noopener noreferrer"&gt;hack/ensure-config.sh&lt;/a&gt;, so that users don't have to manually create the config file, that they have no idea what to write in there. Also I've utilized &lt;code&gt;read&lt;/code&gt; command in bash to make it interactive, so that users can just copy and paste the values when they run &lt;code&gt;make run&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification: Does it actually work?
&lt;/h2&gt;

&lt;p&gt;You can see the verifications from the step above named "Result"&lt;/p&gt;

&lt;h1&gt;
  
  
  What's next?
&lt;/h1&gt;

&lt;p&gt;Now that I have a working prototype built from scratch, I want to bridge the gap between this "clean" version and a robust, production-grade operator. My roadmap for the coming weeks is as follows:&lt;/p&gt;

&lt;h2&gt;
  
  
  I: The "Weekly Dive": Performance Optimization
&lt;/h2&gt;

&lt;p&gt;I plan to dedicate a full week (my "Weekly Dive") to deeply thinking about performance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Optimization Strategy&lt;/strong&gt;: instead of blindly fetching full role memberships every time, I want to investigate using HTTP headers (like ETag or Last-Modified) to implement a delta-sync mechanism.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling Complexity&lt;/strong&gt;: I am also curious about how Assumed Roles affect performance with &lt;code&gt;expand=true&lt;/code&gt; in API and logic compared to standard domain roles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  II: Dissecting the Production Code (athenz/k8s-athenz-syncer)
&lt;/h2&gt;

&lt;p&gt;After my independent study, I will deploy the official upstream Athenz/k8s-athenz-syncer locally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: I aim to document exactly what configurations are required to run it and create easy-to-use manifests so anyone can deploy it effortlessly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Audit&lt;/strong&gt;: I will conduct a line-by-line analysis of the production code. My goal is to reverse-engineer the "why" behind their design decisions—what edge cases did they handle that I missed? Why did they choose specific architectural patterns?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  III: Contributing Back
&lt;/h2&gt;

&lt;p&gt;Finally, I don't just want to be an observer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feedback Loop&lt;/strong&gt;: If I find performance bottlenecks or logic gaps during my audit, I plan to raise Issues or submit PRs to the upstream repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community&lt;/strong&gt;: I hope to start a conversation with the maintainers (Yahoo Inc.) to validate my assumptions and share my findings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guide others&lt;/strong&gt;: If some teams want to use Athenz role as SSoT for Kubernetes RBAC, I want to help them by sharing my learnings and possibly providing a more production-ready version of my "hard &amp;amp; clean way" operator.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Dive Hours: 28 hours
&lt;/h1&gt;

&lt;p&gt;This post took me approximately 28 hours of focused work and development, broken down as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;12/26 Fri&lt;/code&gt;: 4.5h&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;12/27 Sat&lt;/code&gt;: 5.5h&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;12/28 Sun&lt;/code&gt;: 8.5h&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;12/30 Tue&lt;/code&gt;: 9.5h&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Closing
&lt;/h1&gt;

&lt;p&gt;If you enjoyed this deep dive, please leave a like &amp;amp; subscribe for more!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7wqhz26qax25wstwe1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7wqhz26qax25wstwe1z.png" alt="like_this_photo_cat" width="736" height="736"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>operator</category>
      <category>athenz</category>
      <category>k8sathenzsyncer</category>
    </item>
    <item>
      <title>Why this K8s TODO survived 3 years: An Autopsy of fieldnamedocscheck</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Sun, 21 Dec 2025 10:51:08 +0000</pubDate>
      <link>https://forem.com/mlajkim/dives-into-kubernetes-contribution-in-week-50-2025-4i6g</link>
      <guid>https://forem.com/mlajkim/dives-into-kubernetes-contribution-in-week-50-2025-4i6g</guid>
      <description>&lt;h1&gt;
  
  
  Week 50, 2025 Weekly Dive
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
Week 50, 2025 Weekly Dive

&lt;ul&gt;
&lt;li&gt;Goal of this week&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;li&gt;Where do we dive this week?&lt;/li&gt;
&lt;li&gt;What can I do as a first time contributor?&lt;/li&gt;
&lt;li&gt;What is fieldnamedocscheck?&lt;/li&gt;
&lt;li&gt;Where is fieldnamedocscheck used?&lt;/li&gt;
&lt;li&gt;
Zero-brain Run &lt;code&gt;verify-fieldname-docs.sh&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;Dissects each code of the 60 lines of sh&lt;/li&gt;
&lt;li&gt;Dissection: Safety check&lt;/li&gt;
&lt;li&gt;
Dissection: KUBE_ROOT setup

&lt;ul&gt;
&lt;li&gt;Di-Dissection: Can you run somewhere else with this logic?&lt;/li&gt;
&lt;li&gt;We can't fix the path issue&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Dissection: go language check&lt;/li&gt;

&lt;li&gt;Dissection: store every type.go&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;Zero-brain Run fieldnamedocscheck&lt;/li&gt;

&lt;li&gt;What is that &lt;code&gt;-s&lt;/code&gt; flag?&lt;/li&gt;

&lt;li&gt;Can we get a help command for that &lt;code&gt;-s&lt;/code&gt; flag, without looking at the source code?&lt;/li&gt;

&lt;li&gt;Successfully run with &lt;code&gt;-s&lt;/code&gt;!&lt;/li&gt;

&lt;li&gt;Dissects the core logic&lt;/li&gt;

&lt;li&gt;Dissection: Store all the field names in lower case&lt;/li&gt;

&lt;li&gt;Dissection: Understand what's inside the variables&lt;/li&gt;

&lt;li&gt;Check if the go file actually works&lt;/li&gt;

&lt;li&gt;Understand TODOs&lt;/li&gt;

&lt;li&gt;What's next?: Create a sample enforcing mechanism&lt;/li&gt;

&lt;li&gt;Returned 1402 lines of errors just for one file&lt;/li&gt;

&lt;li&gt;What's next?: Create a auto lint fixer&lt;/li&gt;

&lt;li&gt;Create a linter&lt;/li&gt;

&lt;li&gt;Apply the code&lt;/li&gt;

&lt;li&gt;Find the smallest code&lt;/li&gt;

&lt;li&gt;Realize that the changes are too big and go beyond expected&lt;/li&gt;

&lt;li&gt;Let's see if others have worked on this&lt;/li&gt;

&lt;li&gt;Create PR, even if it is WIP&lt;/li&gt;

&lt;li&gt;Fixes CLA problem&lt;/li&gt;

&lt;li&gt;Closes the PR&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;References&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Goal of this week
&lt;/h2&gt;

&lt;p&gt;The goal of this week is to make my first contribution to kubernetes project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I have successfully made a PR here: &lt;a href="https://github.com/kubernetes/kubernetes/pull/135727" rel="noopener noreferrer"&gt;feat(tools): support incremental enforcement of backticks in &lt;code&gt;check-missing-backticks&lt;/code&gt; in &lt;code&gt;field_name_docs_check.go&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do we dive this week?
&lt;/h2&gt;

&lt;p&gt;I want to be a part of Contributor in kubernetes&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fkubernetes_contributors.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fkubernetes_contributors.png" alt="kubernetes_contributors" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And eventually have the &lt;code&gt;@kubernetes&lt;/code&gt; handle in GitHub:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fpersonal_kubernetes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fpersonal_kubernetes.png" alt="personal_kubernetes" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What can I do as a first time contributor?
&lt;/h2&gt;

&lt;p&gt;So I've git cloned the kubernetes repository without thinking too much, but still have no idea what to fix.&lt;/p&gt;

&lt;p&gt;So I asked AI inside the project, what kind of things I can fix as a first time contributor (basically easy stuff)&lt;/p&gt;

&lt;p&gt;And the AI has found this todo inside &lt;code&gt;fieldnamedocscheck&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// TODO: a manual pass adding back-ticks to the doc strings, then update the linter to&lt;/span&gt;
&lt;span class="c"&gt;// TODO: check the existence of back-ticks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First of all I have no idea what this field_name_docs_check.go does so ... let's just dive in without knowing anything :)&lt;/p&gt;

&lt;h2&gt;
  
  
  What is fieldnamedocscheck?
&lt;/h2&gt;

&lt;p&gt;It seems like it checks if the fields in the types are properly documented or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where is fieldnamedocscheck used?
&lt;/h2&gt;

&lt;p&gt;So what is fieldnamedocscheck? Let see where this cmd &lt;code&gt;fieldnamedocscheck&lt;/code&gt; is used:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Ffieldnamedocscheck_usage_search.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Ffieldnamedocscheck_usage_search.png" alt="fieldnamedocscheck_usage_search" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Zero-brain Run &lt;code&gt;verify-fieldname-docs.sh&lt;/code&gt;
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
You may be asked to run &lt;code&gt;brew install bash&lt;/code&gt; if your bash version is insufficient&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This script has checked 64 lines of output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./hack/verify-fieldname-docs.sh
&lt;span class="c"&gt;# Checking ./staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1&lt;/span&gt;
&lt;span class="c"&gt;# Checking ./staging/src/k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1&lt;/span&gt;
&lt;span class="c"&gt;# Checking ./staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1&lt;/span&gt;
&lt;span class="c"&gt;# Checking ./staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1&lt;/span&gt;
&lt;span class="c"&gt;# Checking ./staging/src/k8s.io/api/rbac/v1&lt;/span&gt;
&lt;span class="c"&gt;# Checking ./staging/src/k8s.io/api/rbac/v1beta1&lt;/span&gt;
&lt;span class="c"&gt;# Checking ./staging/src/k8s.io/api/rbac/v1alpha1&lt;/span&gt;
&lt;span class="c"&gt;# Checking ./staging/src/k8s.io/api/apiserverinternal/v1alpha1&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Dissects each code of the 60 lines of sh
&lt;/h4&gt;

&lt;p&gt;Let's open the &lt;code&gt;verify-fieldname-docs.sh&lt;/code&gt; file and dissect each code.&lt;/p&gt;

&lt;h5&gt;
  
  
  Dissection: Safety check
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; errexit
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; nounset
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; pipefail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;errexit&lt;/code&gt; : Exit immediately if a command exits with a non-zero status&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nounset&lt;/code&gt; : Treat unset variables as an error when substituting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pipefail&lt;/code&gt;: Prevent errors in a pipeline from being masked&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Dissection: KUBE_ROOT setup
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;[!TIP]&lt;br&gt;
Once you &lt;code&gt;echo $KUBE_ROOT&lt;/code&gt;, you will get &lt;code&gt;./hack/..&lt;/code&gt; as a sample.&lt;br&gt;
That &lt;code&gt;..&lt;/code&gt; at the end&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KUBE_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BASH_SOURCE&lt;/span&gt;&lt;span class="p"&gt;[0]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/.. &lt;span class="c"&gt;# i.e) ./hack/.. or ../../..&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KUBE_ROOT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/hack/lib/init.sh"&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KUBE_ROOT&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/hack/lib/util.sh"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Di-Dissection: Can you run somewhere else with this logic?
&lt;/h6&gt;

&lt;p&gt;In conclusion, we need to run the &lt;code&gt;verify-fieldname-docs.sh&lt;/code&gt; from the base directory of kubernetes repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./oss_workspace/oss_kubernetes/hack/verify-fieldname-docs.sh
&lt;span class="c"&gt;# KUBE_ROOT=./oss_workspace/oss_kubernetes/hack/..&lt;/span&gt;
&lt;span class="c"&gt;# go: go.mod file not found in current directory or any parent directory; see 'go help modules'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;../../verify-fieldname-docs.sh
&lt;span class="c"&gt;# KUBE_ROOT=../../..&lt;/span&gt;
&lt;span class="c"&gt;# stat ~/oss_workspace/oss_kubernetes/hack/hello/can/cmd/fieldnamedocscheck: directory not found&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./verify-fieldname-docs.sh
&lt;span class="c"&gt;# KUBE_ROOT=./..&lt;/span&gt;
&lt;span class="c"&gt;# stat ~/oss_workspace/oss_kubernetes/hack/cmd/fieldnamedocscheck: directory not found&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  We can't fix the path issue
&lt;/h6&gt;

&lt;p&gt;Since this chunk of code is used, I do not think we can modify this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fthe_same_code_for_source.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fthe_same_code_for_source.png" alt="the_same_code_for_source" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Dissection: go language check
&lt;/h5&gt;

&lt;p&gt;Checks if golang sufficient environment is set&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kube::golang::setup_env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Dissection: store every type.go
&lt;/h5&gt;

&lt;p&gt;Store every &lt;code&gt;type.go&lt;/code&gt; files inside &lt;code&gt;versioned_api_files&lt;/code&gt; variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;versioned_api_files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find_files&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Zero-brain Run fieldnamedocscheck
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
It is important to give a shot even if you don't know what you are doing :)&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ./cmd/fieldnamedocscheck
go run field_name_docs_check.go
&lt;span class="c"&gt;# F1211 11:49:45.848446   80482 field_name_docs_check.go:33] Please define -s flag as it is the api type file&lt;/span&gt;
&lt;span class="c"&gt;# exit status 255&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What is that &lt;code&gt;-s&lt;/code&gt; flag?
&lt;/h2&gt;

&lt;p&gt;Not officially, probably, it is a type source file, it seems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;typeSrc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type-src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"From where we are going to read the types"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;typeSrc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;klog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please define -s flag as it is the api type file"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Can we get a help command for that &lt;code&gt;-s&lt;/code&gt; flag, without looking at the source code?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run field_name_docs_check.go &lt;span class="nt"&gt;--help&lt;/span&gt;
Usage of ~/Library/Caches/go-build/81/..hash../-d/field_name_docs_check:
  &lt;span class="nt"&gt;-s&lt;/span&gt;, &lt;span class="nt"&gt;--type-src&lt;/span&gt; string   From where we are going to &lt;span class="nb"&gt;read &lt;/span&gt;the types
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Successfully run with &lt;code&gt;-s&lt;/code&gt;!
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
If nothing happens, it means all the fields are properly documented :)&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run field_name_docs_check.go &lt;span class="nt"&gt;-s&lt;/span&gt; ../../staging/src/k8s.io/api/core/v1/types.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dissects the core logic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dissection: Store all the field names in lower case
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;typesMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Sample keys:&lt;/span&gt;
&lt;span class="c"&gt;// key: ipfamilypolicy  key: trafficdistribution  key: externalips ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To find all the fields, including right and wrong ones, we store all the field names in lower case inside &lt;code&gt;typesMap&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dissection: Understand what's inside the variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;TCPSocket&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TCPSocketAction&lt;/span&gt; &lt;span class="s"&gt;`json:"tcpSocket,omitempty" protobuf:"bytes,3,opt,name=tcpSocket"`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Struct: TCPSocketAction
p.Name:  tcpSocket
typesMap: tcpsocket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Check if the go file actually works
&lt;/h2&gt;

&lt;p&gt;It seems like it checks only when it has the back-tick quoted field names in the doc string.&lt;/p&gt;

&lt;p&gt;So I've set up &lt;code&gt;TCPSocket&lt;/code&gt; =&amp;gt; &lt;code&gt;TcPSocket&lt;/code&gt;, and created a new sentence &lt;code&gt;// TcPSocket is deprecated and...&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// LifecycleHandler defines a specific action that should be taken in a lifecycle&lt;/span&gt;
&lt;span class="c"&gt;// hook. One and only one of the fields, except `TcPSocket` must be specified.&lt;/span&gt;
&lt;span class="c"&gt;// `TcPSocket` is deprecated and not supported as a LifecycleHandler.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;LifecycleHandler&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c"&gt;// Deprecated. `TcPSocket` is NOT supported as a LifecycleHandler and kept&lt;/span&gt;
  &lt;span class="c"&gt;// for backward compatibility. There is no validation of this field and&lt;/span&gt;
  &lt;span class="c"&gt;// lifecycle hooks will fail at runtime when it is specified.&lt;/span&gt;
  &lt;span class="c"&gt;// +optional&lt;/span&gt;
  &lt;span class="n"&gt;TCPSocket&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;TCPSocketAction&lt;/span&gt; &lt;span class="s"&gt;`json:"tcpSocket,omitempty" protobuf:"bytes,3,opt,name=tcpSocket"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And got two errors (Note that even if there are three places of &lt;code&gt;TcPSocket&lt;/code&gt;, it only shows two errors because the second rule skips the already found mismatched names):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run field_name_docs_check.go &lt;span class="nt"&gt;-s&lt;/span&gt; ../../staging/src/k8s.io/api/core/v1/types.go
&lt;span class="c"&gt;# Error: doc for LifecycleHandler contains: TcPSocket, which should be: tcpSocket&lt;/span&gt;
&lt;span class="c"&gt;# Error: doc for LifecycleHandler.tcpSocket contains: TcPSocket, which should be: tcpSocket&lt;/span&gt;
&lt;span class="c"&gt;# exit status 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understand TODOs
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// The rule is:&lt;/span&gt;
&lt;span class="c"&gt;// 1. Get all back-tick quoted names in the doc&lt;/span&gt;
&lt;span class="c"&gt;// 2. Skip the name which is already found mismatched.&lt;/span&gt;
&lt;span class="c"&gt;// 3. Skip the name whose lowercase is different from the lowercase of tag names,&lt;/span&gt;
&lt;span class="c"&gt;//    because some docs use back-tick to quote field value or nil&lt;/span&gt;
&lt;span class="c"&gt;// 4. Check if the name is different from its tag name&lt;/span&gt;

&lt;span class="c"&gt;// TODO: a manual pass adding back-ticks to the doc strings, then update the linter to&lt;/span&gt;
&lt;span class="c"&gt;// TODO: check the existence of back-ticks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now, setting the &lt;code&gt;back-ticks&lt;/code&gt; is not mandatory, but the TODO suggests that we should add back-ticks to the doc strings, and then update the linter to check the existence of back-ticks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?: Create a sample enforcing mechanism
&lt;/h2&gt;

&lt;p&gt;I created the following enforcing mechanism to make sure all the field names are properly documented with back-ticks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"regexp"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;

    &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="s"&gt;"github.com/spf13/pflag"&lt;/span&gt;
    &lt;span class="n"&gt;kruntime&lt;/span&gt; &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/runtime"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/apimachinery/pkg/util/sets"&lt;/span&gt;
    &lt;span class="s"&gt;"k8s.io/klog/v2"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;typeSrc&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type-src"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"From where we are going to read the types"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;re&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"`(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;w+&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;b)`"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reAllWords&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regexp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustCompile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;w+&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;b"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// kubeTypesMap is a map from field name to its tag name and doc.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;kubeTypesMap&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;kruntime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pair&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;typeSrc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;klog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Please define -s flag as it is the api type file"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;docsForTypes&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;kruntime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseDocumentationFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;typeSrc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ks&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;docsForTypes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;typesMap&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kubeTypesMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// skip the field with no tag name&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;typesMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;structName&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;

        &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;checkFieldNameAndDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;structName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;typesMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;ks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;checkFieldNameAndDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;structName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;typesMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;checkFieldNameAndDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;structName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;typesMap&lt;/span&gt; &lt;span class="n"&gt;kubeTypesMap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="n"&gt;visited&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]{}&lt;/span&gt;

    &lt;span class="c"&gt;// The rule is:&lt;/span&gt;
    &lt;span class="c"&gt;// 1. Get all back-tick quoted names in the doc&lt;/span&gt;
    &lt;span class="c"&gt;// 2. Skip the name which is already found mismatched.&lt;/span&gt;
    &lt;span class="c"&gt;// 3. Skip the name whose lowercase is different from the lowercase of tag names,&lt;/span&gt;
    &lt;span class="c"&gt;//    because some docs use back-tick to quote field value or nil&lt;/span&gt;
    &lt;span class="c"&gt;// 4. Check if the name is different from its tag name&lt;/span&gt;
    &lt;span class="c"&gt;// 5. Check if there are any unquoted field names in the doc&lt;/span&gt;

    &lt;span class="n"&gt;nameGroups&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FindAllStringSubmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nameGroup&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;nameGroups&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;nameGroup&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;typesMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
            &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Error (Case Mismatch): doc for %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;structName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;" contains: %s, which should be: %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;wordIndices&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;reAllWords&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FindAllStringIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;wordIndices&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isField&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;typesMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isField&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;hasBacktick&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'`'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sc"&gt;'`'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;hasBacktick&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;hasBacktick&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
            &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Error (Missing Backticks): doc for %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;structName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;".%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;" mentions field %s without backticks. Should be: `%s`&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Returned 1402 lines of errors just for one file
&lt;/h3&gt;

&lt;p&gt;I got &lt;code&gt;1,402&lt;/code&gt; lines of errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run field_name_docs_check.go &lt;span class="nt"&gt;-s&lt;/span&gt; ../../staging/src/k8s.io/api/core/v1/types.go
&lt;span class="c"&gt;# Error (Missing Backticks): doc for AWSElasticBlockStoreVolumeSource.volumeID mentions field volumeID without backticks. Should be: `volumeID`&lt;/span&gt;
&lt;span class="c"&gt;# Error (Missing Backticks): doc for AWSElasticBlockStoreVolumeSource.fsType mentions field fsType without backticks. Should be: `fsType`&lt;/span&gt;
&lt;span class="c"&gt;# Error (Missing Backticks): doc for AWSElasticBlockStoreVolumeSource.partition mentions field partition without backticks. Should be: `partition`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's next?: Create a auto lint fixer
&lt;/h2&gt;

&lt;p&gt;There is no way that I manually fix &lt;code&gt;1,402&lt;/code&gt; lines of errors, and this applies for only 1 file.&lt;/p&gt;

&lt;p&gt;Also, even after I fix this somehow, if someone else adds a new field without back-ticks, BEFORE I enforce the back-ticks check, the problem will happen again.&lt;/p&gt;

&lt;p&gt;Therefore, I think it is better, if I make a mechanism that enforces one page first, and then eventually scope out to the entire repository.&lt;/p&gt;

&lt;p&gt;Also there are some words like, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt;, etc, that are used very often in the doc strings, but they are ALSO field names. That case, we need to have some kind of AI mechanism to figure out whether it is a field name or just a normal word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a linter
&lt;/h2&gt;

&lt;p&gt;I simply used AI for the linter. I won't put source code because it was not hard to generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Apply the code
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run field_name_docs_check_lint.go &lt;span class="nt"&gt;-s&lt;/span&gt; ../../staging/src/k8s.io/api/core/v1/types.go
&lt;span class="c"&gt;# ✅ Successfully fixed backticks in: ../../staging/src/k8s.io/api/core/v1/types.go&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Find the smallest code
&lt;/h2&gt;

&lt;p&gt;I do not want to scare them but also want them to merge the PR &amp;amp; somewhat has dependency in my changes later in the future.&lt;/p&gt;

&lt;p&gt;So maybe enforcing one file first with the smallest lines of code is a good idea.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find staging/src/k8s.io/api &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"types.go"&lt;/span&gt; | xargs &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 5
&lt;span class="c"&gt;# 48 staging/src/k8s.io/api/authentication/v1alpha1/types.go&lt;/span&gt;
&lt;span class="c"&gt;# 75 staging/src/k8s.io/api/scheduling/v1/types.go&lt;/span&gt;
&lt;span class="c"&gt;# 82 staging/src/k8s.io/api/scheduling/v1beta1/types.go&lt;/span&gt;
&lt;span class="c"&gt;# 83 staging/src/k8s.io/api/imagepolicy/v1alpha1/types.go&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Realize that the changes are too big and go beyond expected
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// `azureDisk` represents an Azure Data Disk mount on the host and bind mount to the pod.&lt;/span&gt;
&lt;span class="c"&gt;// Deprecated: `azureDisk` is deprecated. All operations for the in-tree `azureDisk` type&lt;/span&gt;
&lt;span class="c"&gt;// are redirected to the disk.`csi`.azure.com `csi` driver.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem of the changes above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The link is corrupted because of the back-ticks: &lt;code&gt;disk.&lt;/code&gt;csi&lt;code&gt;.azure.com&lt;/code&gt; =&amp;gt; should be &lt;code&gt;disk.csi.azure.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Not sure if we can start with &lt;code&gt;azureDisk&lt;/code&gt; rather than &lt;code&gt;AzureDisk&lt;/code&gt; for the first word

&lt;ul&gt;
&lt;li&gt;It seems like the preview mode works fine, but not sure if the actual kubernetes doc generator works fine with this.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Let's see if others have worked on this
&lt;/h2&gt;

&lt;p&gt;I was not able to find one&lt;/p&gt;

&lt;h2&gt;
  
  
  Create PR, even if it is WIP
&lt;/h2&gt;

&lt;p&gt;It won't build but who cares. Do the zero-brain PR: &lt;a href="https://github.com/kubernetes/kubernetes/pull/135727" rel="noopener noreferrer"&gt;https://github.com/kubernetes/kubernetes/pull/135727&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Ffirst_time_pr_in_kubernetes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Ffirst_time_pr_in_kubernetes.png" alt="first_time_pr_in_kubernetes" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I see you &lt;code&gt;Kubernetes Contributor&lt;/code&gt; badge :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fkubernetes_badge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fkubernetes_badge.png" alt="kubernetes_badge" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixes CLA problem
&lt;/h2&gt;

&lt;p&gt;I got this warning in my PR:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fsigned_agreement_missing.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fsigned_agreement_missing.png" alt="signed_agreement_missing" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Did the sign here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fdocument_sign_cla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fdocument_sign_cla.png" alt="document_sign_cla" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it says:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fcla_signed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fcla_signed.png" alt="cla_signed" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Closes the PR
&lt;/h2&gt;

&lt;p&gt;My goal was to create a PR out there and has definitely experienced the process of contribution.&lt;/p&gt;

&lt;p&gt;But I realize that why such a TODO has been out there for 3 years without being fixed; The fix required too many changes that goes beyond the expected range like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;url corruption&lt;/li&gt;
&lt;li&gt;handle general terms like port, name, type etc that are used very often in the doc strings&lt;/li&gt;
&lt;li&gt;passes the k8s native rules for doc generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I close the PR:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fclosed_pr_with_comments.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/.%2Fassets%2Fclosed_pr_with_comments.png" alt="closed_pr_with_comments" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;p&gt;NONE&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>opensource</category>
      <category>tutorial</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How 'cd' actually works in Linux Kernel: A Source Code Analysis</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Sun, 30 Nov 2025 03:07:18 +0000</pubDate>
      <link>https://forem.com/mlajkim/251130-zsh-cd-460i</link>
      <guid>https://forem.com/mlajkim/251130-zsh-cd-460i</guid>
      <description>&lt;h1&gt;
  
  
  Goal
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;[!NOTE]&lt;br&gt;
This dive took 6.5 hours&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The goal of this document is to dive deep into the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /
&lt;span class="nb"&gt;cd&lt;/span&gt; -
&lt;span class="nb"&gt;cd
cd&lt;/span&gt; ..
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Target Audience
&lt;/h1&gt;

&lt;p&gt;This document is intended for users who have a basic understanding of Linux command line operations and wish to deepen their knowledge of directory navigation commands, with simple copy-paste examples.&lt;/p&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Goal&lt;/li&gt;
&lt;li&gt;Target Audience&lt;/li&gt;
&lt;li&gt;TOC&lt;/li&gt;
&lt;li&gt;Backgrounds&lt;/li&gt;
&lt;li&gt;
Dive in

&lt;ul&gt;
&lt;li&gt;How to check source code of &lt;code&gt;cd&lt;/code&gt; command?&lt;/li&gt;
&lt;li&gt;How do we know which shell we are using?&lt;/li&gt;
&lt;li&gt;Let's quickly check the version&lt;/li&gt;
&lt;li&gt;Dive into the source code of &lt;code&gt;cd&lt;/code&gt;'s &lt;code&gt;bin_cd&lt;/code&gt; command in &lt;code&gt;zsh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Restricted shell check&lt;/li&gt;
&lt;li&gt;
cd with chasing symbolic links

&lt;ul&gt;
&lt;li&gt;setopt&lt;/li&gt;
&lt;li&gt;How to check the options set in your shell?&lt;/li&gt;
&lt;li&gt;How to check what options are not yet enabled in your shell?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Signal handling for control the signals during &lt;code&gt;cd&lt;/code&gt; execution&lt;/li&gt;

&lt;li&gt;zpushnode(dirstack, ztrdup(pwd));&lt;/li&gt;

&lt;li&gt;

Dive into &lt;code&gt;cd_get_dest()&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;if (!argv[0])&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Dive into cd_new_pwd()&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

Others

&lt;ul&gt;
&lt;li&gt;First time seeing 25 years old commit&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Backgrounds
&lt;/h1&gt;

&lt;p&gt;If you have had a chance to work with Linux, the commands above are pretty common for navigating through directories. You may already know what they do, but let's dive deep into each of them to understand their behaviors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cd /:&lt;/code&gt; Navigates to the Root directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cd ..:&lt;/code&gt; Navigates to the Parent directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cd:&lt;/code&gt; Navigates to the Home directory (equivalent to &lt;code&gt;cd ~&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cd -:&lt;/code&gt; Navigates to the Previous directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While the first three are straightforward, &lt;code&gt;cd -&lt;/code&gt; behaves like a "Back" button. But how does the shell remember where I was?&lt;/p&gt;

&lt;h1&gt;
  
  
  Dive in
&lt;/h1&gt;

&lt;h2&gt;
  
  
  How to check source code of &lt;code&gt;cd&lt;/code&gt; command?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;type cd&lt;/span&gt;
&lt;span class="c"&gt;# cd is a shell builtin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;shell builtin&lt;/code&gt; means that the command &lt;code&gt;cd&lt;/code&gt; is implemented directly within the shell (like &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;zsh&lt;/code&gt;, etc.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we know which shell we are using?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SHELL&lt;/span&gt;
&lt;span class="c"&gt;# /bin/zsh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR you may:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;
&lt;span class="c"&gt;# -zsh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's quickly check the version
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/zsh &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# or: `zsh --version`&lt;/span&gt;
&lt;span class="c"&gt;# zsh 5.9 (arm64-apple-darwin24.0)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dive into the source code of &lt;code&gt;cd&lt;/code&gt;'s &lt;code&gt;bin_cd&lt;/code&gt; command in &lt;code&gt;zsh&lt;/code&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;[!INFO]&lt;br&gt;
The following is a mirror link of the official repository of &lt;code&gt;zsh&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/zsh-users/zsh" rel="noopener noreferrer"&gt;https://github.com/zsh-users/zsh&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The core functions are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cd_get_dest()&lt;/code&gt;: does the initial argument processing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cd_do_chdir()&lt;/code&gt;: actually changes directory, if possible&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cd_new_pwd()&lt;/code&gt;: does the ancillary processing associated with actually changing directory&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Restricted shell check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RESTRICTED&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;zwarnnam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"restricted"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[!INFO]&lt;br&gt;
zwarnnam is a compound word for: &lt;code&gt;z&lt;/code&gt;(ZSH) + &lt;code&gt;warn&lt;/code&gt; (WARNING) + &lt;code&gt;nam&lt;/code&gt; (NAME)&lt;br&gt;
A function for error output with consistent formatting.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This code checks if the shell is running in restricted mode by using the &lt;code&gt;isset(RESTRICTED)&lt;/code&gt; function. If it is, it issues a warning message "restricted" using the &lt;code&gt;zwarnnam&lt;/code&gt; function and returns &lt;code&gt;1&lt;/code&gt;, indicating an error. In restricted mode, certain commands, including changing directories - &lt;code&gt;cd&lt;/code&gt; -, may be limited to enhance security.&lt;/p&gt;

&lt;p&gt;Will return the following output when you try to use &lt;code&gt;cd&lt;/code&gt; in restricted mode:&lt;/p&gt;

&lt;p&gt;Prepare restricted shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;zsh &lt;span class="nt"&gt;-r&lt;/span&gt;
&lt;span class="c"&gt;# shell_session_history_enable:1: /usr/bin/touch: restricted&lt;/span&gt;
&lt;span class="c"&gt;# shell_session_history_enable:2: HISTFILE: restricted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# cd ..&lt;/span&gt;
zsh: &lt;span class="nb"&gt;cd&lt;/span&gt;: restricted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  cd with chasing symbolic links
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;chasinglinks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OPT_ISSET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'P'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CHASELINKS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;OPT_ISSET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sc"&gt;'L'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;chasinglinks&lt;/code&gt; is a boolean value, meaning is_chasing_symbolic_links or not.&lt;br&gt;
It is &lt;code&gt;true&lt;/code&gt; if :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If user specifies &lt;code&gt;-P&lt;/code&gt; option&lt;/li&gt;
&lt;li&gt;If user has a preference setting CHASELINKS enabled

&lt;ul&gt;
&lt;li&gt;You can temporarily override this setting by specifying &lt;code&gt;-L&lt;/code&gt; option&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;tmp_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%y%m%d_%H%M%S_test&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nv"&gt;$tmp_dir&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$tmp_dir&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;physical_path
&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; physical_path symbolic_link
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check Setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-al&lt;/span&gt;
&lt;span class="c"&gt;# total 0&lt;/span&gt;
&lt;span class="c"&gt;# drwxr-xr-x   4 ajk  staff   128 Nov 29 13:58 .&lt;/span&gt;
&lt;span class="c"&gt;# drwxr-x---+ 73 ajk  staff  2336 Nov 29 13:54 ..&lt;/span&gt;
&lt;span class="c"&gt;# drwxr-xr-x   3 ajk  staff    96 Nov 29 13:58 physical_path&lt;/span&gt;
&lt;span class="c"&gt;# lrwxr-xr-x   1 ajk  staff    13 Nov 29 13:58 symbolic_link -&amp;gt; physical_path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;symbolic_link
&lt;span class="nb"&gt;pwd&lt;/span&gt;
&lt;span class="c"&gt;# /path/to/your/tmp_dir/symbolic_link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test with &lt;code&gt;-P&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; symbolic_link
&lt;span class="nb"&gt;pwd&lt;/span&gt;
&lt;span class="c"&gt;# /path/to/your/tmp_dir/physical_path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test with &lt;code&gt;CHASE_LINKS&lt;/code&gt; enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
setopt CHASE_LINKS
&lt;span class="nb"&gt;cd &lt;/span&gt;symbolic_link
&lt;span class="nb"&gt;pwd&lt;/span&gt;
&lt;span class="c"&gt;# /path/to/your/tmp_dir/physical_path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Override the &lt;code&gt;CHASE_LINKS&lt;/code&gt; with &lt;code&gt;-L&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; symbolic_link
&lt;span class="nb"&gt;pwd&lt;/span&gt;
&lt;span class="c"&gt;# /path/to/your/tmp_dir/symbolic_link&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  setopt
&lt;/h4&gt;

&lt;p&gt;the command &lt;code&gt;setopt&lt;/code&gt; is in &lt;code&gt;Src/options.c&lt;/code&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  How to check the options set in your shell?
&lt;/h5&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* With no arguments or options, display options. */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;scanhashtable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optiontab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OPT_ALIAS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;optiontab&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;printnode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;isun&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;setopt
&lt;span class="c"&gt;# chaselinks&lt;/span&gt;
&lt;span class="c"&gt;# combiningchars&lt;/span&gt;
&lt;span class="c"&gt;# interactive&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How to check what options are not yet enabled in your shell?
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unsetopt
&lt;span class="c"&gt;# noaliases&lt;/span&gt;
&lt;span class="c"&gt;# aliasfuncdef&lt;/span&gt;
&lt;span class="c"&gt;# allexport&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Signal handling for control the signals during &lt;code&gt;cd&lt;/code&gt; execution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;queue_signals&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure that other signals (like interrupts) do not interfere with the execution of the &lt;code&gt;cd&lt;/code&gt; command. This is important because changing directories is a critical operation, and we want to ensure it completes without interruption.&lt;/p&gt;

&lt;h3&gt;
  
  
  zpushnode(dirstack, ztrdup(pwd));
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;pwd&lt;/code&gt; contains the current working directory before changing it.&lt;/p&gt;

&lt;p&gt;You can check &lt;code&gt;dirstack&lt;/code&gt; by:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;dirs&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
0   ~/Workspaces
1   ~/Workspaces/oss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dive into &lt;code&gt;cd_get_dest()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;cd_get_dest&lt;/code&gt; returns the destination directory based on the arguments provided to the &lt;code&gt;cd&lt;/code&gt; command. It handles various cases, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No arguments: returns the home directory&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&lt;/code&gt;: returns the previous directory from the directory stack&lt;/li&gt;
&lt;li&gt;Specific path: returns the specified path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It returns &lt;code&gt;NULL&lt;/code&gt; if there is an error, such as when the previous directory is not set.&lt;/p&gt;

&lt;p&gt;Also it is shared by the following commands (Please note that they had to do this for performance/size optimization back in the days when memory and storage were more limited):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pushd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;popd&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  if (!argv[0])
&lt;/h4&gt;

&lt;p&gt;If argument is empty:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[!INFO]&lt;br&gt;
&lt;code&gt;popd&lt;/code&gt; stands for "pop directory" and is used to remove the top entry from the directory stack and change the current working directory to that entry.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;BIN_POPD&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;nextnode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstnode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dirstack&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;zwarnnam&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"directory stack empty"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can manipulate this error log when your directory stack is empty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# dirs -v&lt;/span&gt;
&lt;span class="c"&gt;# 0 ~/Workspaces/directory&lt;/span&gt;
&lt;span class="nb"&gt;popd&lt;/span&gt;
&lt;span class="c"&gt;# popd: directory stack empty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This part checks if the first argument to the &lt;code&gt;cd&lt;/code&gt; command is a hyphen (&lt;code&gt;-&lt;/code&gt;). If it is, the function retrieves the previous directory from the directory stack. If the stack is empty, it issues a warning and returns &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dive into cd_new_pwd()
&lt;/h3&gt;

&lt;p&gt;Basically write the new working directory to the &lt;code&gt;PWD&lt;/code&gt; environment variable and update the directory stack accordingly.&lt;/p&gt;

&lt;h1&gt;
  
  
  Others
&lt;/h1&gt;

&lt;h2&gt;
  
  
  First time seeing 25 years old commit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0h3fv9mh7l921erllmr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft0h3fv9mh7l921erllmr.png" alt=" " width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cli</category>
      <category>tutorial</category>
      <category>linux</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Hello World</title>
      <dc:creator>Jeongwoo Kim</dc:creator>
      <pubDate>Fri, 28 Nov 2025 05:59:11 +0000</pubDate>
      <link>https://forem.com/mlajkim/hello-world-mbj</link>
      <guid>https://forem.com/mlajkim/hello-world-mbj</guid>
      <description>&lt;p&gt;This is my first post in dev.to&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
