<?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>DEV Community: Cheedge Lee</title>
    <description>The latest articles on DEV Community by Cheedge Lee (@cheedge_lee).</description>
    <link>https://dev.to/cheedge_lee</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%2F2219656%2F91091e8e-317c-4c66-bad3-2ca410cbd7db.jpg</url>
      <title>DEV Community: Cheedge Lee</title>
      <link>https://dev.to/cheedge_lee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cheedge_lee"/>
    <language>en</language>
    <item>
      <title>CKS Notes - Audit log</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Fri, 28 Nov 2025 21:14:12 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-audit-log-4593</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-audit-log-4593</guid>
      <description>&lt;p&gt;This is a quick notes and summary from office doc.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;this is a quick look at log audit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;more detail about the log audit, pls refer to the official doc about &lt;a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/audit/" rel="noopener noreferrer"&gt;audit&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;and also can check my previous article: &lt;a href="https://notes-renovation.hashnode.dev/kubernets-related-logs-configurations" rel="noopener noreferrer"&gt;kubernets related logs &amp;amp; configurations&lt;/a&gt; which shows clear log file paths and the config related files and their locations.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Audit workflow
&lt;/h2&gt;

&lt;p&gt;edit the &lt;code&gt;audit-policy.yaml&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;audit.k8s.io/v1&lt;/span&gt; &lt;span class="c1"&gt;# This is required.&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;Policy&lt;/span&gt;
&lt;span class="c1"&gt;# Don't generate audit events for all requests in RequestReceived stage.&lt;/span&gt;
&lt;span class="na"&gt;omitStages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RequestReceived"&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Log pod changes at RequestResponse level&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RequestResponse&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="c1"&gt;# Resource "pods" doesn't match requests to any subresource of pods,&lt;/span&gt;
      &lt;span class="c1"&gt;# which is consistent with the RBAC policy.&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="c1"&gt;# Log "pods/log", "pods/status" at Metadata level&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Metadata&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods/log"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods/status"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Don't log requests to a configmap called "controller-leader"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configmaps"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;resourceNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;controller-leader"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Don't log watch requests by the "system:kube-proxy" on endpoints or services&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
    &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system:kube-proxy"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# core API group&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;endpoints"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;services"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Don't log authenticated requests to certain non-resource URL paths.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
    &lt;span class="na"&gt;userGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system:authenticated"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;nonResourceURLs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api*"&lt;/span&gt; &lt;span class="c1"&gt;# Wildcard matching.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/version"&lt;/span&gt;

  &lt;span class="c1"&gt;# Log the request body of configmap changes in kube-system.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Request&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# core API group&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configmaps"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# This rule only applies to resources in the "kube-system" namespace.&lt;/span&gt;
    &lt;span class="c1"&gt;# The empty string "" can be used to select non-namespaced resources.&lt;/span&gt;
    &lt;span class="na"&gt;namespaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kube-system"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Log configmap and secret changes in all other namespaces at the Metadata level.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Metadata&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# core API group&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secrets"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configmaps"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="c1"&gt;# Log all other resources in core and extensions at the Request level.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Request&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# core API group&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;extensions"&lt;/span&gt; &lt;span class="c1"&gt;# Version of group should NOT be included.&lt;/span&gt;

  &lt;span class="c1"&gt;# A catch-all rule to log all other requests at the Metadata level.&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Metadata&lt;/span&gt;
    &lt;span class="c1"&gt;# Long-running requests like watches that fall under this rule will not&lt;/span&gt;
    &lt;span class="c1"&gt;# generate an audit event in RequestReceived.&lt;/span&gt;
    &lt;span class="na"&gt;omitStages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RequestReceived"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;add flags to &lt;code&gt;/etc/kubernetes/manifests/kube-apiserver.yaml&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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kube-apiserver&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--audit-policy-file=/etc/kubernetes/audit-policy.yaml&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--audit-log-path=/var/log/kubernetes/audit/audit.log&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--audit-log-maxage=8&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--audit-log-maxsize=9&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--audit-log-maxbackup=3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--audit-policy-file=/etc/kubernetes/audit-policy.yaml&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--audit-log-path=/var/log/kubernetes/audit/audit.log&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--audit-log-maxage=8&lt;/code&gt; : max number of days&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--audit-log-maxsize=9&lt;/code&gt; : max file size in megabytes before rotate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--audit-log-maxbackup=3&lt;/code&gt; : copy of audit log files&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;edit the volumeMounts&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;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/kubernetes/audit-policy.yaml&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;audit&lt;/span&gt;
    &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/log/kubernetes/audit/&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;audit-log&lt;/span&gt;
    &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;edit the &lt;code&gt;hostPath&lt;/code&gt; corresponding with &lt;code&gt;--audit-policy-file&lt;/code&gt; and &lt;code&gt;--audit-log-path&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;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;audit&lt;/span&gt;
  &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&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;/etc/kubernetes/audit-policy.yaml&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;File&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;audit-log&lt;/span&gt;
  &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&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;/var/log/kubernetes/audit/&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DirectoryOrCreate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then wait for some time and we can check the logs on path &lt;code&gt;/var/log/kubernetes/audit/&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Notice:
&lt;/h3&gt;

&lt;p&gt;in above Volume and VolumeMount, we see some difference settings for audit policy and audit logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit policy&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Path is a single YAML &lt;strong&gt;file&lt;/strong&gt;: &lt;code&gt;/etc/kubernetes/audit-policy.yaml&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;readOnly&lt;/code&gt;: API server only needs to &lt;em&gt;read&lt;/em&gt; it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;type: File&lt;/code&gt;: The file &lt;strong&gt;already exists,&lt;/strong&gt; Kubelet should fail to start the apiserver if the file is missing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Audit logs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Path is a &lt;strong&gt;directory&lt;/strong&gt; &lt;code&gt;/var/log/kubernetes/audit/&lt;/code&gt;, API server writes out &lt;strong&gt;multiple log files&lt;/strong&gt; (with rotation) &lt;code&gt;audit.log&lt;/code&gt;, &lt;code&gt;audit.log.1&lt;/code&gt;, etc&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;readOnly: false&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;type: DirectoryOrCreate&lt;/code&gt;: It may not exist yet on a fresh system, Kubelet will automatically create it so the API server can write logs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Policy
&lt;/h2&gt;

&lt;p&gt;A Kubernetes audit policy has &lt;strong&gt;three important layers&lt;/strong&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Resources you target&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;    &lt;span class="c1"&gt;# core API group&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configmaps"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;secrets"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;persistentvolumeclaims"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;...&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;group: ""&lt;/code&gt; means &lt;strong&gt;Core API group&lt;/strong&gt;, like pods, configmaps, secrets, services, pv, pvc.&lt;/p&gt;

&lt;p&gt;but there are also non-core-api&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;deployments&lt;/td&gt;
&lt;td&gt;apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;daemonsets&lt;/td&gt;
&lt;td&gt;apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jobs&lt;/td&gt;
&lt;td&gt;batch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ingresses&lt;/td&gt;
&lt;td&gt;&lt;a href="http://networking.k8s.io" rel="noopener noreferrer"&gt;networking.k8s.io&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;certificatesigningrequests&lt;/td&gt;
&lt;td&gt;&lt;a href="http://certificates.k8s.io" rel="noopener noreferrer"&gt;certificates.k8s.io&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;so the resource eg.&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;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps"&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployments"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. &lt;strong&gt;Which stages you log&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Request"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Response"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Metadata"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Request&lt;/strong&gt;: log the incoming request before it hits handlers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Response&lt;/strong&gt;: log the full response (including responseBody if enabled)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Metadata&lt;/strong&gt;: only logs the request metadata (verb, user, resource, namespace)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Normally not used, but we often define &lt;code&gt;omitStages&lt;/code&gt; to reduce noise.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;omitStages: ["RequestReceived"]&lt;/code&gt; is used to: &lt;strong&gt;Reduce extremely noisy logs with no loss of useful information.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RequestReceived&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;kube-apiserver got the request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseStarted&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;apiserver is sending stream response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ResponseComplete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;finished sending full response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Panic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;apiserver crashed during request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;Log level fields&lt;/strong&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None | Metadata | Request | RequestResponse&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3.1. level:&lt;/strong&gt; &lt;code&gt;None&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;No audit event produced, which is used to ignore noisy resources. eg.&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;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;level:&lt;/strong&gt; &lt;code&gt;Metadata&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Logs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;user (e.g., system:serviceaccount:default:sa)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;verb (get, list, update, patch…)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;resource (configmaps, pods…)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;namespace&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;request URI&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;response code&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No request/response bodies. This is lightweight and safe for Secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.2. level:&lt;/strong&gt; &lt;code&gt;Request&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Logs everything from “Metadata” &lt;strong&gt;plus the request body&lt;/strong&gt;. eg.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the new version of a ConfigMap&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the PVC spec submitted by kubectl&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the PATCH operations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Does NOT log the response body.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.3. level:&lt;/strong&gt; &lt;code&gt;RequestResponse&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Logs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;metadata&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;request body&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;response body&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;eg. when we run command &lt;code&gt;kubectl apply -f my-pv.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;the audit logs include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the PV YAML sent&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the PV YAML that apiserver stored after admission&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the most expensive but also the most complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Logs&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;None&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;nothing&lt;/td&gt;
&lt;td&gt;Useful to exclude noisy resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metadata&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;verb, user, resource, namespace, stage&lt;/td&gt;
&lt;td&gt;Very cheap, no bodies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Request&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;request headers + request body&lt;/td&gt;
&lt;td&gt;More expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RequestResponse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;request + response body&lt;/td&gt;
&lt;td&gt;Most expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>cks</category>
      <category>kubernetes</category>
      <category>audit</category>
      <category>policy</category>
    </item>
    <item>
      <title>CKS Notes - work node update</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Fri, 28 Nov 2025 17:21:19 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-work-node-update-36h5</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-work-node-update-36h5</guid>
      <description>&lt;p&gt;Here just a quick present of basic workflow of how to upgrade on the &lt;strong&gt;work&lt;/strong&gt; &lt;strong&gt;node&lt;/strong&gt;.More details can refer to the official doc, &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/upgrading-linux-nodes/" rel="noopener noreferrer"&gt;upgrading Linux work node&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice: The official doc shows is the completed procedural which we will stop the jobs, and drain the node(s) then continue the upgrade, but here we do the upgrade one work node without disturbing the jobs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1. check the nodes we will upgrade
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;controlplane:~&lt;span class="nv"&gt;$ &lt;/span&gt;k get no
NAME           STATUS   ROLES           AGE   VERSION
controlplane   Ready    control-plane   10d   v1.34.2
node01         Ready    &amp;lt;none&amp;gt;          10d   v1.34.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. ssh to the node and check the version
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;controlplane:~&lt;span class="nv"&gt;$ &lt;/span&gt;ssh node01
node01:~&lt;span class="nv"&gt;$ &lt;/span&gt;apt-cache madison kubeadm
   kubeadm | 1.34.2-1.1 | https://pkgs.k8s.io/core:/stable:/v1.34/deb  Packages
   kubeadm | 1.34.1-1.1 | https://pkgs.k8s.io/core:/stable:/v1.34/deb  Packages
   kubeadm | 1.34.0-1.1 | https://pkgs.k8s.io/core:/stable:/v1.34/deb  Packages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. ssh to work node, do the upgrade
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node01:~&lt;span class="nv"&gt;$ &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;kubeadm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.34.2-1.1
node01:~&lt;span class="nv"&gt;$ &lt;/span&gt;kubeadm upgrade node
node01:~&lt;span class="nv"&gt;$ &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;&lt;span class="nv"&gt;kubelet&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.34.2-1.1 &lt;span class="nv"&gt;kubectl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.34.2-1.1
node01:~&lt;span class="nv"&gt;$ &lt;/span&gt;systemctl restart kubelet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. back to controlplane and check the result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node01:~&lt;span class="nv"&gt;$ &lt;/span&gt;ssh controlplane
controlplane:~&lt;span class="nv"&gt;$ &lt;/span&gt;k get node
NAME           STATUS   ROLES           AGE   VERSION
controlplane   Ready    control-plane   10d   v1.34.2
node01         Ready    &amp;lt;none&amp;gt;          10d   v1.34.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cluster upgrade
&lt;/h2&gt;

&lt;p&gt;If need to upgrade the controlplane node, see the article &lt;a href="https://notes-renovation.hashnode.dev/kubenetes-cluster-nodes-related-issues#heading-2-cluster" rel="noopener noreferrer"&gt;here&lt;/a&gt;, or the official doc about &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/" rel="noopener noreferrer"&gt;upgrade kubeadm&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>kubeadm</category>
      <category>kubectl</category>
      <category>kubelet</category>
    </item>
    <item>
      <title>CKS Notes - TLS</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Thu, 27 Nov 2025 20:40:34 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-tls-bll</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-tls-bll</guid>
      <description>&lt;h2&gt;
  
  
  TLS
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# generate TLS cert and key&lt;/span&gt;
openssl &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; options ... &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; parameters ... &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. TLS config files
&lt;/h2&gt;

&lt;p&gt;About TLS we will mainly focus on 3 levels/types encryption in this article.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;inside cluster communication (&lt;code&gt;/etc/kubernetes/pki/*&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;client (&lt;code&gt;kubectl&lt;/code&gt;) communicate with apiserver(&lt;code&gt;~/.kube/config&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;application level communication (TLS Secrets)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1.1 PKI files
&lt;/h3&gt;

&lt;p&gt;PKI files in &lt;code&gt;/etc/kubernetes/pki/&lt;/code&gt; secure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;kubelet ↔ apiserver (mTLS)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scheduler ↔ apiserver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;controller-manager ↔ apiserver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;apiserver ↔ etcd&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;(in some setups) kube-proxy ↔ apiserver&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These ensure &lt;strong&gt;control-plane communication is always encrypted and authenticated.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2 Kubectl using TLS
&lt;/h3&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;~/.kube/config&lt;/code&gt; stores:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the API server URL (HTTPS)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the CA certificate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;user’s client certificate/key&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;kubectl connects to apiserver using TLS&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;apiserver knows which user is calling&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1.3 Application level (TLS Secrets)
&lt;/h3&gt;

&lt;p&gt;Stored inside Kubernetes as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: testsecret-tls
  namespace: default
data:
  tls.crt: &lt;span class="nb"&gt;base64 &lt;/span&gt;encoded cert
  tls.key: &lt;span class="nb"&gt;base64 &lt;/span&gt;encoded key
&lt;span class="nb"&gt;type&lt;/span&gt;: kubernetes.io/tls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Used by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Ingress controllers (public HTTPS)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gateways (Istio)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These certificates belong to &lt;strong&gt;applications&lt;/strong&gt;, not Kubernetes.&lt;/p&gt;

&lt;h4&gt;
  
  
  1.3.1 TLS Secret and usage
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create secret tls &amp;lt;NAME&amp;gt; &lt;span class="nt"&gt;--cert&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/PATH/TP/&amp;lt;CERT&amp;gt; &lt;span class="nt"&gt;--key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/PATH/TO/&amp;lt;KEY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the Ingress use it&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;networking.k8s.io/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;Ingress&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;tls-example-ingress&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;https-example.foo.com&lt;/span&gt;
    &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;testsecret-tls&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then we can use &lt;code&gt;curl&lt;/code&gt; to verify&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;curl -kv https://...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;more details check official doc&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets" rel="noopener noreferrer"&gt;TLS Secrets&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress/#tls" rel="noopener noreferrer"&gt;Ingress use TLS&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and more details about the secrets, can also check previous blog&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://notes-renovation.hashnode.dev/kubernetes-secret-1" rel="noopener noreferrer"&gt;Kubernetes Secret (1)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://dev.to/cheedge_lee/kubernets-secret-2-20ec"&gt;Kubernetes Secret (2)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. TLS Flags
&lt;/h2&gt;

&lt;p&gt;TLS flags apply to the server's accepted TLS standards, and the type of certificate (&lt;code&gt;kubeconfig&lt;/code&gt; cert, &lt;code&gt;PKI&lt;/code&gt; cert, bootstrap cert) doesn’t matter, as long as the TLS connection follows the rules.&lt;/p&gt;

&lt;p&gt;ALL these must use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;allowed TLS version&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;allowed cipher suites&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;supported handshake parameters&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2.1 TLS Flags of apiserver
&lt;/h3&gt;

&lt;p&gt;apiserver is a TLS server and TLS flags define what ALL clients must follow.&lt;/p&gt;

&lt;p&gt;Clients include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;kubectl → apiserver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;kubelet → apiserver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;controller-manager → apiserver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;scheduler → apiserver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;operators → apiserver&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&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="nt"&gt;--tls-min-version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;VersionTLS13
&lt;span class="nt"&gt;--tls-cipher-suites&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TLS_AES_128_GCM_SHA256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2 TLS Flags of etcd
&lt;/h3&gt;

&lt;p&gt;Example:&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="nt"&gt;--cipher-suites&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These apply to:&lt;/p&gt;

&lt;p&gt;Clients connecting to etcd:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;kube-apiserver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;etcdctl (if used)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;backup/restore tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;any etcd peer connections&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reference
&lt;/h3&gt;

&lt;p&gt;The exact flags can refer to the k8s (&lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/" rel="noopener noreferrer"&gt;kube-apiserver&lt;/a&gt;) and etcd (&lt;a href="https://etcd.io/docs/v3.6/op-guide/security/" rel="noopener noreferrer"&gt;security&lt;/a&gt;) official docs.&lt;/p&gt;

</description>
      <category>cks</category>
      <category>tls</category>
      <category>ingress</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>CKS Notes - Image Security</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Thu, 27 Nov 2025 10:21:32 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-image-security-29k6</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-image-security-29k6</guid>
      <description>&lt;h2&gt;
  
  
  ImagePolicyWebhook
&lt;/h2&gt;

&lt;p&gt;Like &lt;code&gt;NodeRestriction&lt;/code&gt;, &lt;code&gt;ImagePolicyWebhook&lt;/code&gt; is also a “Admission Controller” plugin.&lt;/p&gt;

&lt;p&gt;As we said before, the “Admission Controller” works after authentication and authorization (RBAC).&lt;/p&gt;

&lt;p&gt;Authentication → Authorization → Admission ( NodeRestriction/ImagePolicyWebhook)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;NodeRestriction&lt;/code&gt; : Prevent nodes from modifying objects they are not allowed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ImagePolicyWebhook&lt;/code&gt; : Validate container images used in Pods before the pod is admitted&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically it checks before Kubernetes allows a Pod to run, and call an external image security service and ask: &lt;em&gt;Is this image allowed?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. External service
&lt;/h3&gt;

&lt;p&gt;Here the external server works as:&lt;/p&gt;

&lt;p&gt;Kubernetes API server performs an HTTP POST to &lt;strong&gt;an external webhook server&lt;/strong&gt; (HTTPS endpoint) to ask:&lt;br&gt;&lt;br&gt;
“Is this image allowed?”&lt;/p&gt;

&lt;p&gt;That server might be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A company’s internal image scanner&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A vulnerability scanner (Trivy server)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A signature validator (Notary v1/2, Cosign webhook)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A custom Python/Go program&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A local service on the same node (like &lt;code&gt;https://localhost:1234&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A remote service outside the cluster&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The API server only sends image info.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The external service decides what to check.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Admission Controller Configure file
&lt;/h3&gt;

&lt;p&gt;the admission controller configure file can be either in yaml/json&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiVersion"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apiserver.config.k8s.io/v1"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kind"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AdmissionConfiguration"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plugins"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
    &lt;span class="pi"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ImagePolicyWebhook"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;configuration"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;imagePolicy"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubeConfigFile"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/path/to/&amp;lt;kubeconfig_file&amp;gt;"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allowTTL"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;100&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;denyTTL"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;50&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retryBackoff"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;500&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultAllow"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;false&lt;/span&gt;
        &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;kubeConfigFile&lt;/code&gt; The kubeconfig file used by API server to call the external webhook&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;allowTTL&lt;/code&gt; Cache “allowed” decisions for 100 seconds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;denyTTL&lt;/code&gt; Cache “denied” decisions for 50 seconds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;retryBackoff&lt;/code&gt; If webhook fails, wait 500ms before retrying&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;defaultAllow&lt;/code&gt; = false If webhook cannot be reached → deny pods by default (strict mode)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. KubeConfigFile
&lt;/h3&gt;

&lt;p&gt;The kubeconfig tells the API server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Which webhook server to call&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which TLS certs to use for mutual TLS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which CA to trust for server verification&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;clusters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;certificate-authority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/kubernetes/policywebhook/external-cert.pem&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://localhost:1234&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;image-checker&lt;/span&gt;

&lt;span class="na"&gt;users&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;api-server&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;client-certificate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/kubernetes/policywebhook/apiserver-client-cert.pem&lt;/span&gt;
    &lt;span class="na"&gt;client-key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;/etc/kubernetes/policywebhook/apiserver-client-key.pem&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;API server must contact a webhook running on &lt;code&gt;https://localhost:1234&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API server will verify the webhook using the provided CA cert.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API server authenticates &lt;strong&gt;itself&lt;/strong&gt; to the webhook using mutual TLS.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. manifest kube-apiserver.yaml
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/etc/kubernetes/manifest/kube-apiserver.yaml&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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;kube-apiserver&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--enable-admission-plugins=NodeRestriction,ImagePolicyWebhook&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--admission-control-config-file=/PATH/TO/&amp;lt;ADMISSION_CONTROLLER_CONFIG_FILE&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;# eg. /etc/kubernetes/policywebhook/admission_config.json&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reference
&lt;/h3&gt;

&lt;p&gt;Official doc: &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#imagepolicywebhook" rel="noopener noreferrer"&gt;Imagepolicywebhook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also can check the examples &lt;a href="https://killercoda.com/killer-shell-cks/scenario/image-policy-webhook-setup" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Trivy Image scan
&lt;/h2&gt;

&lt;p&gt;Before we talk about the Trivy can be the external server to check the images. Here we will see how to use Trivy to scan images.&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;# find the images&lt;/span&gt;
k get pod &lt;span class="nt"&gt;-oyaml&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;image:
k describe pod |grep &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'^Name:|Image:'&lt;/span&gt;

&lt;span class="c"&gt;# find the high,critical images&lt;/span&gt;
trivy image &lt;span class="nt"&gt;-s&lt;/span&gt; HIGH,CRITICAL &amp;lt;IMAGE_NAME&amp;gt;:&amp;lt;VERSION&amp;gt;
&lt;span class="c"&gt;# we can also show the specified Vulnerability&lt;/span&gt;
trivy image &lt;span class="nt"&gt;-s&lt;/span&gt; HIGH,CRITICAL &amp;lt;IMAGE_NAME&amp;gt;:&amp;lt;VERSION&amp;gt; |grep &amp;lt;Vulnerability&amp;gt;
&lt;span class="c"&gt;# eg.&lt;/span&gt;
trivy image nginx:1.19.1-alpine-perl | &lt;span class="nb"&gt;grep &lt;/span&gt;CVE-2021
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the image with vulnerabilites, and we can either&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;remove the pod or scale down the deployment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;patch the source of the image Dockerfile&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# remove the pod or scale down the dp/sc&lt;/span&gt;
k &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;NAMESPACE&amp;gt; delete pod &amp;lt;NAME&amp;gt;
k &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;NAMESPACE&amp;gt; scale deploy &amp;lt;DEPLOY&amp;gt; &lt;span class="nt"&gt;--replicas&lt;/span&gt; 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;remove the pod or scale down the dp is just temporal way, in reality we still need to find a image which is no vulnerabilities to patch the source&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;# 1. find a base image&lt;/span&gt;
Report Summary

┌────────────────────────────────┬────────┬─────────────────┬─────────┐
│             Target             │  Type  │ Vulnerabilities │ Secrets │
├────────────────────────────────┼────────┼─────────────────┼─────────┤
│ morc-api:0.1-5 &lt;span class="o"&gt;(&lt;/span&gt;alpine 3.22.1&lt;span class="o"&gt;)&lt;/span&gt; │ alpine │       25        │    -    │
└────────────────────────────────┴────────┴─────────────────┴─────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from the Report summary we can know it is based on alpine, so we can find the latest version for patch, but first check it.&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;$ &lt;/span&gt;trivy image nginx:alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then build the image,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;IMAGE&amp;gt;:&amp;lt;PATCHED_VERSION&amp;gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &amp;lt;Dockerfile&amp;gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And as Kubernetes &lt;strong&gt;does not use Docker anymore&lt;/strong&gt;.(Kubernetes ≥ 1.24 does NOT run Docker.)&lt;/p&gt;

&lt;p&gt;It uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;containerd&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or &lt;strong&gt;CRI-O&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So even if we have built the image using &lt;code&gt;docker build&lt;/code&gt;, that image exists only in &lt;strong&gt;Docker’s local storage&lt;/strong&gt;, not in the cluster runtime.&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;p&gt;docker build → Docker local image store&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NOT visible to &lt;code&gt;Kubernetes&lt;/code&gt; / &lt;code&gt;containerd&lt;/code&gt; / &lt;code&gt;CRI-O&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore we need to do following steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Save the Docker image&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker save &amp;lt;IMAGE_NAME&amp;gt;:&amp;lt;PATCHED_VERSION&amp;gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &amp;lt;IMAGE_NAME&amp;gt;_&amp;lt;PATCHED_VERSION&amp;gt;.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Import into containerd&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ctr &lt;span class="nt"&gt;-n&lt;/span&gt; k8s.io images import &amp;lt;IMAGE_NAME&amp;gt;_&amp;lt;PATCHED_VERSION&amp;gt;.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This loads the image into the runtime used by kubelet → &lt;code&gt;containerd&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Check image in containerd&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crictl images | &lt;span class="nb"&gt;grep&lt;/span&gt; &amp;lt;IMAGE_NAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If they do NOT import the image:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kubelet won’t see the patched image&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;containerd&lt;/code&gt; will try to pull the old image from registry&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Trivy scanning &lt;strong&gt;containerd&lt;/strong&gt; won’t see the patched image&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reference:
&lt;/h3&gt;

&lt;p&gt;Can check an example &lt;a href="https://killercoda.com/ronnyardi/scenario/scs_sbom" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>trivy</category>
      <category>imagepolicywebhook</category>
      <category>admissioncontroller</category>
    </item>
    <item>
      <title>CKS Notes - some notes on docker(podman)</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Wed, 26 Nov 2025 16:22:08 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-some-notes-on-dockerpodman-gmk</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-some-notes-on-dockerpodman-gmk</guid>
      <description>&lt;h2&gt;
  
  
  1. Basic CMD
&lt;/h2&gt;

&lt;p&gt;These basic cmd are same structure for both &lt;code&gt;docker&lt;/code&gt; and &lt;code&gt;podman&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;# build image&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; &amp;lt;NAME&amp;gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /PATH/TO/Dockerfile &amp;lt;BUILD_CONTEXT&amp;gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;docker image &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# run docker container -d detached&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; &amp;lt;NAME&amp;gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &amp;lt;BASE_IMAGE&amp;gt;

&lt;span class="c"&gt;# check running contianers&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker ps

&lt;span class="c"&gt;# check process in container&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &amp;lt;CONTAINER_NAME&amp;gt; ps
&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &amp;lt;CONTAINER_NAME&amp;gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;&amp;lt;BUILD_CONTEXT&amp;gt;&lt;/code&gt;: the &lt;strong&gt;directory/path&lt;/strong&gt; that Docker sends to the daemon, containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;files referenced by &lt;code&gt;COPY&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;files referenced by &lt;code&gt;ADD&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;anything needed during build&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, &lt;code&gt;BUILD_CONTEXT&lt;/code&gt; is the dir/path when the Dockerfile try to do the “COPY” or “ADD” operations it will searched dir/path.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.1 Tags
&lt;/h3&gt;

&lt;p&gt;if you build the same image using &lt;code&gt;docker build -t &amp;lt;SAME_NAME&amp;gt; -f /PATH/TO/Dockerfile &amp;lt;BUILD_CONTEXT&amp;gt;&lt;/code&gt;, it will build a new image, the old one’s tag will be removed as &lt;code&gt;&amp;lt;none&amp;gt;&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;$ &lt;/span&gt;docker image &lt;span class="nb"&gt;ls
&lt;/span&gt;REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
base-image   latest    2487f22f50cb   5 seconds ago    5.58MB
&amp;lt;none&amp;gt;       &amp;lt;none&amp;gt;    e9a4699a5cbb   17 minutes ago   5.58MB
&amp;lt;none&amp;gt;       &amp;lt;none&amp;gt;    8a8330c3730a   23 minutes ago   5.58MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1.2 Layers (Caching)
&lt;/h3&gt;

&lt;p&gt;More layers → more caching → faster builds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker creates a new layer for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;RUN&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;COPY&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ADD&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;BUT NOT for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CMD&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ENTRYPOINT&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ENV&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;EXPOSE&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;USER&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;WORKDIR&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice: &lt;code&gt;CMD&lt;/code&gt; and &lt;code&gt;ENTRYPOINT&lt;/code&gt; DO NOT create layers. They are metadata, not filesystem layers.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apk update
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add curl
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each of these caches independently.&lt;/p&gt;

&lt;p&gt;If merging them together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk add curl git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;strong&gt;any small change breaks the entire cache&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So sometimes &lt;strong&gt;more layers = faster builds&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice: I don’t mean more layers are always good, more layers will create more temporal artifacts. So reduce layers where possible, but caching and maintainability matter more than minimizing layers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  1.3 share PID
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
docker run &lt;span class="nt"&gt;--name&lt;/span&gt; sidecar &lt;span class="nt"&gt;--pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;container:app &lt;span class="nt"&gt;-d&lt;/span&gt; busybox &lt;span class="nb"&gt;sleep &lt;/span&gt;infinity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&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;example&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;shareProcessNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;containers&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;app&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;nginx&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;sidecar&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;busybox&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sleep"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3600"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Dockerfile Best Practice - Multi-stage build
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.24&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /src&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; &amp;lt;&amp;lt;EOF ./main.go&lt;/span&gt;
package main

import "fmt"

func main() {
  fmt.Println("hello, world")
}
EOF
&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; /bin/hello ./main.go

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=0 /bin/hello /bin/hello&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/bin/hello"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the second &lt;code&gt;FROM scratch&lt;/code&gt; will build on first &lt;code&gt;FROM golang:1.24&lt;/code&gt;, as “The &lt;code&gt;COPY --from=0&lt;/code&gt; line copies just the built artifact from the previous stage into this new stage.”&lt;/p&gt;

&lt;p&gt;more details check &lt;a href="https://docs.docker.com/build/building/multi-stage/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Dockerfile Best Practice - Run as non-root user
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;-S&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    adduser &lt;span class="nt"&gt;-S&lt;/span&gt; appuser &lt;span class="nt"&gt;-G&lt;/span&gt; appgroup &lt;span class="nt"&gt;-h&lt;/span&gt; /home/appuser
&lt;span class="c"&gt;# or for Alphine&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;adduser &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; appuser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are secure as long as you do USER appuser.&lt;/p&gt;

&lt;p&gt;The explicit version gives more control, but the simpler version is secure enough for typical apps.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-D&lt;/code&gt; : create user with no password, no shell, minimal config&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;-g ''&lt;/code&gt; : empty GECOS field&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Dockerfile Best Practice - Never echo secrets in Dockerfile
&lt;/h2&gt;

&lt;p&gt;example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;Layer 1: RUN echo $TOKEN &amp;gt; /tmp/token
Layer 2: RUN sh -c 'register.sh /tmp/token'
Layer 3: RUN rm /tmp/token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;each &lt;code&gt;RUN&lt;/code&gt; will create a new layer, so if we use cmd to save “TOKEN”, which means it will be saved inside that layer, even then using &lt;code&gt;RUN rm&lt;/code&gt; command. Therefore, &lt;strong&gt;DO NOT&lt;/strong&gt; save secrets in Dockerfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1.2&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret,id&lt;span class="o"&gt;=&lt;/span&gt;token &lt;span class="se"&gt;\
&lt;/span&gt;    sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'cat /run/secrets/token | register.sh'&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;register.sh &lt;span class="nv"&gt;$TOKEN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For better practice, secret can be passed into the container during runtime as env variable &lt;code&gt;TOKEN&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Docker related Security best practice
&lt;/h2&gt;

&lt;p&gt;basic configuration files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/usr/lib/systemd/system/docker.socket&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/usr/lib/systemd/system/docker.service&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;if you forgot the path, just check their status, eg. &lt;code&gt;systemctl status docker.socket&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After changing the setting, remember to restart corresponding service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;systemctl daemon-reload 
systemctl restart docker.socket 
systemctl restart docker.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.1 Do NOT expose Docker over TCP
&lt;/h3&gt;

&lt;p&gt;Otherwise it will expose ROOT ACCESS without authentication.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;/usr/lib/systemd/system/docker.service&lt;/code&gt;, remove &lt;code&gt;-H tcp://0.0.0.0:2375&lt;/code&gt; from &lt;code&gt;ExecStart&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# remove -H tcp://0.0.0.0:2375&lt;/span&gt;

&lt;span class="err"&gt;[&lt;/span&gt;Service]
&lt;span class="nc"&gt;Type&lt;/span&gt;=notify
&lt;span class="c"&gt;# the default is not to use systemd for cgroups because the delegate issues still&lt;/span&gt;
&lt;span class="c"&gt;# exists and systemd currently does not support the cgroup feature set required&lt;/span&gt;
&lt;span class="c"&gt;# for containers run by docker&lt;/span&gt;
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;check the TCP&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ss &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;dockerd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.2 Enforce TLS if you must expose TCP
&lt;/h3&gt;

&lt;p&gt;If remote Docker API is absolutely required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;enable TLS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;require certificates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;never use TCP without TLS&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="err"&gt;-&lt;/span&gt;H tcp://127.0.0.1:2376
&lt;span class="err"&gt;--&lt;/span&gt;tlsverify
&lt;span class="err"&gt;--&lt;/span&gt;tlscacert=ca.pem
&lt;span class="err"&gt;--&lt;/span&gt;tlscert=server-cert.pem
&lt;span class="err"&gt;--&lt;/span&gt;tlskey=server-key.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.3 Run dockerd as root (default), but avoid giving docker group to users
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;docker group = root equivalent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;in &lt;code&gt;/usr/lib/systemd/system/docker.socket&lt;/code&gt; set the&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="err"&gt;[&lt;/span&gt;Unit]
&lt;span class="nc"&gt;Description&lt;/span&gt;=Docker Socket for the API

&lt;span class="err"&gt;[&lt;/span&gt;Socket]
ListenStream=/run/docker.sock
SocketMode=0660
SocketUser=root
SocketGroup=docker
&lt;span class="c"&gt;# either set SocketGroup=root or docker &lt;/span&gt;

&lt;span class="err"&gt;[&lt;/span&gt;Install]
WantedBy=sockets.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never add regular users to docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# check user belonging group&lt;/span&gt;
group &amp;lt;USER_NAME&amp;gt; # eg. group docker

&lt;span class="c"&gt;# show all groups&lt;/span&gt;
getent group
vi /etc/group

&lt;span class="c"&gt;# print real and effective user and group IDs&lt;/span&gt;
id &amp;lt;USER&amp;gt;
vi /etc/passwd
&lt;span class="c"&gt;# $ id ubuntu&lt;/span&gt;
&lt;span class="c"&gt;# uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd)&lt;/span&gt;

&lt;span class="c"&gt;# delete teh user from group&lt;/span&gt;
gpasswd -d &amp;lt;USER&amp;gt; &amp;lt;FROM_GROUP&amp;gt;
gpasswd -d &amp;lt;USER&amp;gt; docker
groupdel docker
&lt;span class="c"&gt;# then socket file created with group = root&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.4 Use AppArmor or SELinux
&lt;/h3&gt;

&lt;p&gt;Hardens container isolation.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# --security-opt apparmor=default&lt;/span&gt;
docker run --security-opt apparmor=myprofile ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;RHEL-based:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# --security-opt label=type:container_t&lt;/span&gt;
docker run --security-opt label:type:svirt_lxc_net_t ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is similar as Kubernetes-level AppArmor&lt;/p&gt;

&lt;p&gt;In Kubernetes, we do NOT call docker run. We must annotate the 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="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container.apparmor.security.beta.kubernetes.io/mycontainer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost/myprofile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.5 Restrict container capabilities
&lt;/h3&gt;

&lt;p&gt;Capabilities = parts of root’s power.&lt;/p&gt;

&lt;p&gt;Remove everything, then add only needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which is similar with Kubernetes-level capabilities&lt;/p&gt;

&lt;p&gt;In Pods:&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;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NET_BIND_SERVICE"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kubernetes simply &lt;strong&gt;passes these to the container runtime&lt;/strong&gt; (Docker, containerd, CRI-O).&lt;/p&gt;

&lt;h3&gt;
  
  
  5.6 Make &lt;code&gt;/var/lib/docker&lt;/code&gt; read-only
&lt;/h3&gt;

&lt;p&gt;Prevents tampering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;--read-only
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.7 Enable logging limits
&lt;/h3&gt;

&lt;p&gt;Avoid log spamming or disk filling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>docker</category>
      <category>podman</category>
      <category>daemon</category>
      <category>dockerfile</category>
    </item>
    <item>
      <title>CKS Notes - Pod Security</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Tue, 25 Nov 2025 19:29:17 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-pod-security-kfh</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-pod-security-kfh</guid>
      <description>&lt;p&gt;For Pod Security, there are basically two layers/types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pod security settings for each pod&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;namespace labels for Pod Security Admission (PSA)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Layer A: Pod security settings&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Defined in:&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;spec.containers[*].securityContext&lt;/span&gt;
&lt;span class="s"&gt;spec.securityContext&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;container-specific + pod-wide&lt;/strong&gt; security.&lt;/p&gt;

&lt;p&gt;It sets the &lt;strong&gt;runtime security&lt;/strong&gt; for a container.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;runAsNonRoot&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;runAsUser&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;allowPrivilegeEscalation&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;readOnlyRootFilesystem&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;capabilities&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;seccompProfile&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;privileged&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;fsGroup&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Layer B — Namespace-level Pod Security Admission (PSA)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Defined using labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pod-security.kubernetes.io/enforce: restricted|baseline|privileged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets &lt;strong&gt;global rules&lt;/strong&gt; for all pods in the namespace &lt;strong&gt;before&lt;/strong&gt; creation.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;restricted&lt;/code&gt; : strongest security, disallow most risky features&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;baseline&lt;/code&gt; : medium security&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;privileged&lt;/code&gt; : allow almost anything&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;work together&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;PSA (namespace label) is the &lt;strong&gt;policy gate&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pod securityContext is the &lt;strong&gt;actual behavior&lt;/strong&gt; inside the pod&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if the pod sets something insecure, PSA can block it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practice harden rules
&lt;/h2&gt;

&lt;p&gt;Here are the some pod hardening rules for best practice:&lt;/p&gt;

&lt;h2&gt;
  
  
  0. Namespace
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;restricted&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;1. Run as non-root&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid privileged UID 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;2. Disallow privilege escalation&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Stops processes from gaining higher privileges (e.g., via setuid binaries).&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;3. Drop unnecessary Linux capabilities&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add only what you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;4. Use a read-only root filesystem&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;readOnlyRootFilesystem&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prevents writes to &lt;code&gt;/&lt;/code&gt;, reduces persistence and attacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;5. Use seccomp (system call filtering)&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;seccompProfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RuntimeDefault&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prevents dangerous syscalls.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;6. Avoid privileged containers&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;privileged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never run privileged unless absolutely needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;7. Use AppArmor (if available)&lt;/strong&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;container.apparmor.security.beta.kubernetes.io/nginx&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;runtime/default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;8. Restrict host access&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Avoid these unless needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hostNetwork&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hostPID&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hostIPC&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hostPath&lt;/code&gt; volumes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;p&gt;Official doc:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-standards/" rel="noopener noreferrer"&gt;Pod Security Standards&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/security/pod-security-admission/" rel="noopener noreferrer"&gt;Pod Security Admission&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-admission-controller/" rel="noopener noreferrer"&gt;Enforce Pod Security Standards by Configuring the Built-in Admission Controller&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels/" rel="noopener noreferrer"&gt;Enforce Pod Security Standards with Namespace Labels&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>cks</category>
      <category>securitycontext</category>
      <category>kubernetes</category>
      <category>psa</category>
    </item>
    <item>
      <title>CKS Notes - ServiceAccount</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Mon, 24 Nov 2025 19:46:03 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-serviceaccount-4788</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-serviceaccount-4788</guid>
      <description>&lt;p&gt;Previous Notes about the &lt;a href="https://notes-renovation.hashnode.dev/cks-notes-apiserver-request-security" rel="noopener noreferrer"&gt;Apiserver request security&lt;/a&gt; we have shown two identities (node identity &lt;code&gt;/etc/kubernetes/kubelet.conf&lt;/code&gt; and admin cluster identity &lt;code&gt;~/.kube/config&lt;/code&gt; ). Now we will talk about another identity, it is used for the Pods — ServiceAccount. Actually we have talked about ServiceAccount before in previous post about RBAC(for more details about RBAC pls check &lt;a href="https://notes-renovation.hashnode.dev/a-glance-of-kubernetes-role-based-access-control-rbac" rel="noopener noreferrer"&gt;here&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  RBAC
&lt;/h2&gt;

&lt;p&gt;RBAC is an authorization process, which will grant the Service Account with permissions (Role, ClusterRole).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SA - RoleBinding - Role → allowed actions&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ServiceAccount&lt;/strong&gt;: identity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Role&lt;/strong&gt; or &lt;strong&gt;ClusterRole&lt;/strong&gt; : ns-scoped or cluster-scoped allowed operations (permissions)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;RoleBinding, ClusterRoleBinding&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;quick recap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;k create clusterrole NAME &lt;span class="nt"&gt;--verb&lt;/span&gt; OPS1,OPS2 &lt;span class="nt"&gt;--resource&lt;/span&gt; RESOURCENAME1,RESOURCENAME2
k create rolebinding NAME &lt;span class="nt"&gt;--role&lt;/span&gt; ROLENAME &lt;span class="nt"&gt;--serviceaccount&lt;/span&gt; SANAME
&lt;span class="c"&gt;# do not miss up the --as and --service (--as is kubectl param used for test)&lt;/span&gt;
k auth can-i VERB RESOURCE &lt;span class="nt"&gt;--as&lt;/span&gt; system:serviceaccount:NAMESPACE:NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pod → Apiserver
&lt;/h2&gt;

&lt;p&gt;Any pod can connect to the API server, but without RBAC permissions it cannot do anything meaningful.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Pod (no API server access)&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Do &lt;strong&gt;not bind SA&lt;/strong&gt; to any role (default sa)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ideally &lt;strong&gt;disable token mounting&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pod runs with minimal risk&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Pod → API Server&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Create custom ServiceAccount&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bind it to Role/ClusterRole&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Assign that SA to the pod(s)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For case 1, we need to opt out the of API credential auto-mounting&lt;/p&gt;

&lt;p&gt;either set on sa&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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&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;build-robot&lt;/span&gt;
&lt;span class="na"&gt;automountServiceAccountToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or set on the pod needed&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;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&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;my-pod&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build-robot&lt;/span&gt;
  &lt;span class="na"&gt;automountServiceAccountToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;more details pls refer to the official docs “&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-api-credential-automounting" rel="noopener noreferrer"&gt;opt out of API credential auto mounting&lt;/a&gt;”.&lt;/p&gt;

&lt;h4&gt;
  
  
  Verify mount
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# if not mount, will show nothing&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nb"&gt;test exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; podA &lt;span class="nt"&gt;--&lt;/span&gt; mount | &lt;span class="nb"&gt;grep &lt;/span&gt;serviceaccount
&lt;span class="c"&gt;# otherwise, if it still mount the token will show sth like:&lt;/span&gt;
tmpfs on /run/secrets/kubernetes.io/serviceaccount &lt;span class="nb"&gt;type &lt;/span&gt;tmpfs &lt;span class="o"&gt;(&lt;/span&gt;ro,relatime,size&lt;span class="o"&gt;=&lt;/span&gt;2097536k,inode64,noswap&lt;span class="o"&gt;)&lt;/span&gt;

kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nb"&gt;test exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; podA &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /var/run/secrets/kubernetes.io/serviceaccount/token
&lt;span class="c"&gt;# cat: /var/run/secrets/kubernetes.io/serviceaccount/token: No such file or directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Apiserver ←→ Kubelet (Pod)
&lt;/h2&gt;

&lt;p&gt;API server communicates with pods indirectly via &lt;code&gt;kubelet&lt;/code&gt; and &lt;code&gt;kubelet&lt;/code&gt; has its own identity (client certificate from &lt;code&gt;kubelet.conf&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;API server tells kubelet to create a pod&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API server watches pod status&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API server updates pod info&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubelet sends logs to API server&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubelet mounts volumes/secrets&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Kubelet reports liveness/readiness&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API server fetches metrics from kubelet&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Identities comparision
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Human / Automation (kubectl, CI)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Identity comes from &lt;strong&gt;kubeconfig&lt;/strong&gt;, using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;client cert&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;or token (OIDC, bearer)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Kubelet (Node agent)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Identity comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/etc/kubernetes/kubelet.conf&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;client certificate&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User identity: &lt;code&gt;system:node:&amp;lt;nodeName&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Group: &lt;code&gt;system:nodes&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Pod (Workload)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Identity comes from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ServiceAccount token&lt;/strong&gt; projected by kubelet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Identity becomes: &lt;code&gt;system:serviceaccount:&amp;lt;ns&amp;gt;:&amp;lt;name&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Workflow
&lt;/h2&gt;

&lt;p&gt;SA token is a JWT token, so basically we can understand this process as a JWT token verify process.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. send request
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;k get pod -n test&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;then &lt;code&gt;kubectl&lt;/code&gt; actually send the request&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;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;pods&lt;/span&gt;
&lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Bearer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;JWT_TOKEN&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That token is a &lt;strong&gt;JWT&lt;/strong&gt; with 3 parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Header&lt;/strong&gt; – algorithm &amp;amp; token type&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Payload (claims)&lt;/strong&gt; – SA identity information&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Signature&lt;/strong&gt; – signed with API server private key&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Payload includes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;iss&lt;/code&gt; → issuer (kubernetes.default.svc)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;sub&lt;/code&gt; → service account identity (&lt;code&gt;system:serviceaccount:&amp;lt;namespace&amp;gt;:&amp;lt;sa-name&amp;gt;&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://kubernetes.io/serviceaccount/namespace" rel="noopener noreferrer"&gt;&lt;code&gt;kubernetes.io/serviceaccount/namespace&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="http://kubernetes.io/serviceaccount/uid" rel="noopener noreferrer"&gt;&lt;code&gt;kubernetes.io/serviceaccount/uid&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Expiry, audience, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Apiserver handle
&lt;/h3&gt;

&lt;h4&gt;
  
  
  2.1 Authentication
&lt;/h4&gt;

&lt;p&gt;look for key to verify the SA JWT token, if it is authenticated, go next&lt;/p&gt;

&lt;h4&gt;
  
  
  2.2 Authorisation
&lt;/h4&gt;

&lt;p&gt;RBAC authorization&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;request verb = &lt;code&gt;get&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;resource = &lt;code&gt;pods&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;namespace = &lt;code&gt;test&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;user = &lt;code&gt;system:serviceaccount:test:&amp;lt;sa-name&amp;gt;&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2.3 Admission Control (NodeRestriction)
&lt;/h4&gt;

&lt;h4&gt;
  
  
  2.4 Do the Operation
&lt;/h4&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;ServiceAccount:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pod Identity (JWT token)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;SA - RoleBinding - Role → allowed actions&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;automountServiceAccountToken&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pod → Apiserver&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apiserver verify token&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;verify &lt;code&gt;kubectl -n test exec -it podA -- mount | grep serviceaccount&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>serviceaccount</category>
      <category>cks</category>
      <category>kubernetes</category>
      <category>rbac</category>
    </item>
    <item>
      <title>CKS Notes - Apiserver request security</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Sun, 23 Nov 2025 21:17:54 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-apiserver-request-security-3jfc</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-apiserver-request-security-3jfc</guid>
      <description>&lt;h1&gt;
  
  
  1. Identity = kubeconfig
&lt;/h1&gt;

&lt;p&gt;Here the identity equals kubeconfig file&lt;/p&gt;

&lt;p&gt;Every kubeconfig file specifies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;the &lt;strong&gt;certificate&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the &lt;strong&gt;client key&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the &lt;strong&gt;username (CN)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the &lt;strong&gt;group (O)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;the &lt;strong&gt;cluster endpoint&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This determines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Who you are&lt;/strong&gt; (identity)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What you can do&lt;/strong&gt; (RBAC permissions)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let’s first distinguish 2 types of identity: &lt;strong&gt;admin cluster identity&lt;/strong&gt; vs &lt;strong&gt;node identity&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.1 kubelet.conf
&lt;/h2&gt;

&lt;p&gt;Location:&lt;/p&gt;

&lt;p&gt;each nodes &lt;code&gt;/etc/kubernetes/kubelet.conf&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Identity inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;user &lt;span class="o"&gt;=&lt;/span&gt; system:node:&amp;lt;nodeName&amp;gt;
group &lt;span class="o"&gt;=&lt;/span&gt; system:nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This identity belongs to &lt;strong&gt;the kubelet on that node&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Permissions (through RBAC):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;can update its &lt;strong&gt;own&lt;/strong&gt; node object&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;can update &lt;strong&gt;pod status&lt;/strong&gt; for pods assigned to it&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cannot &lt;code&gt;get&lt;/code&gt;/&lt;code&gt;delete&lt;/code&gt;/&lt;code&gt;list&lt;/code&gt; arbitrary pods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;cannot label other nodes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;This identity is heavily restricted by NodeRestriction.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1.2 &lt;code&gt;$HOME/.kube/config&lt;/code&gt; &lt;strong&gt;(&lt;/strong&gt;&lt;code&gt;admin.conf&lt;/code&gt; &lt;strong&gt;copy)&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Location:&lt;/p&gt;

&lt;p&gt;on control plane node&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;user &lt;span class="o"&gt;=&lt;/span&gt; kubernetes-admin
group &lt;span class="o"&gt;=&lt;/span&gt; system:masters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meaning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This is the &lt;strong&gt;cluster admin identity&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Full power (cluster-admin cluster role)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;can do “everything” — (&lt;code&gt;get&lt;/code&gt;/&lt;code&gt;delete&lt;/code&gt;/&lt;code&gt;list&lt;/code&gt;/&lt;code&gt;create&lt;/code&gt;/&lt;code&gt;patch&lt;/code&gt; ANY resource)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;NodeRestriction does NOT apply to this identity.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Workflows
&lt;/h1&gt;

&lt;p&gt;there is one section about &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#admission-control-phases" rel="noopener noreferrer"&gt;Admission control phase&lt;/a&gt;, but it may not easy to understand. Here is my previous post for the workflow (&lt;a href="https://dev.to/cheedge_lee/kubernets-work-flow-5da8"&gt;Kubernetes workflow&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  2.1 Basic concepts
&lt;/h2&gt;

&lt;p&gt;Let’s first have a overlook of some basic components.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubelet&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;runs the containers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;updates pod/node status&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send requests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;kubelet.conf&lt;/code&gt; = identity of kubelet&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used by kubelet when it talks to API server, (NodeRestriction)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;admin.conf / ~/.kube/config&lt;/code&gt; = identity of cluster admin&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used by kubectl&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2.2 Diff workflows
&lt;/h2&gt;

&lt;p&gt;Now let’s see the workflow for diff process&lt;/p&gt;

&lt;h3&gt;
  
  
  API request:
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kubectl → API server → etcd&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pod scheduling:
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;scheduler → API server → etcd&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pod execution:
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kubelet → containerd → pod runs&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Status updates:
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;kubelet → API server → etcd&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Controllers maintain desired state:
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;controller manager → API server → etcd&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  3. Security
&lt;/h1&gt;

&lt;p&gt;the basic security flow is Authentication - Authorization - (Admission Control)&lt;/p&gt;

&lt;h2&gt;
  
  
  3.1 Authentication (Identity)
&lt;/h2&gt;

&lt;p&gt;Here we see when using these 2 type (node identity, cluster identity) config file their workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When human/admin runs &lt;code&gt;kubectl&lt;/code&gt; (&lt;strong&gt;on controlplane&lt;/strong&gt;)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# eg. on controlplane&lt;/span&gt;
k get node
k label node node01 &lt;span class="nb"&gt;test&lt;/span&gt;/thislabel&lt;span class="o"&gt;=&lt;/span&gt;123
&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;0. ~/.kube/config

1. kubectl sends HTTPS request with admin cert

2. API server authenticates admin identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;When kubelet talks to API server (&lt;strong&gt;on each node&lt;/strong&gt;)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# eg. on node01&lt;/span&gt;
k label node node01 &lt;span class="nb"&gt;test&lt;/span&gt;/thislabel&lt;span class="o"&gt;=&lt;/span&gt;123 &lt;span class="nt"&gt;--kubeconfig&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/kubernetes/kubelet.conf
&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;0. /etc/kubernetes/kubelet.conf  &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;local &lt;/span&gt;to that node&lt;span class="o"&gt;)&lt;/span&gt;

1. kubelet loads cert/key

2. kubelet sends HTTPS request to API server

3. API server authenticates &lt;span class="s2"&gt;"system:node:NODENAME"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt; sends requests for user operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;kubelet&lt;/code&gt; sends requests for node/pod management&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3.2 Authorization (RBAC)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Uses CN and O from the certificate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Checks “is this identity allowed to do this verb on this resource?”&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  RBAC uses this identity to check:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Which User?  eg. &lt;code&gt;system:node:node01&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Which Groups? eg. &lt;code&gt;system:nodes&lt;/code&gt; (all kubelets), &lt;code&gt;system:authenticated&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Which verb/resource? 

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;update node/worker1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patch node/worker1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get pods&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RBAC rules that apply to those groups 

&lt;ul&gt;
&lt;li&gt;eg. Kubernetes has a built-in ClusterRole&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;more details can refer to official doc or previous post &lt;a href="https://dev.to/cheedge_lee/a-glance-of-kubernetes-rbac-547a"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3.3 Admission Control (NodeRestriction)
&lt;/h2&gt;

&lt;p&gt;Check the data/operations that try to modify the resource in the arriving request. Prevents kubelets from doing dangerous modifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Node labels&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Node annotations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Node taints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pods on other nodes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More details can check in official doc “&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers" rel="noopener noreferrer"&gt;Admission Control in Kubernetes&lt;/a&gt;” and “&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/node/" rel="noopener noreferrer"&gt;Using Node Authorization&lt;/a&gt;”&lt;/p&gt;

</description>
      <category>apiserver</category>
      <category>rbac</category>
      <category>kubernetes</category>
      <category>kubelet</category>
    </item>
    <item>
      <title>CKS Notes -- Kube-bench</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Fri, 21 Nov 2025 10:02:03 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/cks-notes-kube-bench-2pcj</link>
      <guid>https://dev.to/cheedge_lee/cks-notes-kube-bench-2pcj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;With the experience shared by people on the internet, I summarized some key aspects as a series of articles for preparing the CKS exam. Each aspects should be in short concise points, so this is not a detailed tutorial, just some practical reminders.&lt;/p&gt;

&lt;p&gt;Notice: some concepts are based on my understanding, it may be not accurate or even correct, therefore this is just a handbook when I was preparing the CKS exam.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;kube-bench&lt;/code&gt; is a tool to check if the k8s cluster fulfilled the CIS security benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Basic Command:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh NODE
kube-bench run &lt;span class="nt"&gt;--targets&lt;/span&gt; TARGETS &lt;span class="nt"&gt;--check&lt;/span&gt; VERSION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  params:
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. check &lt;code&gt;--targets&lt;/code&gt;:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;master&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;node&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;controlplane&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;etcd&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;policies&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  2. check CIS version &lt;code&gt;--check&lt;/code&gt;
&lt;/h4&gt;

&lt;h2&gt;
  
  
  Checking items
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. on &lt;code&gt;master&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kube-bench run &lt;span class="nt"&gt;--targets&lt;/span&gt; master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Apiserver (&lt;code&gt;/etc/kubernetes/manifests/kube-apiserver.yaml&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ControllerManager (&lt;code&gt;/etc/kubernetes/manifests/kube-controller-manager.yaml&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;PKI directory (&lt;code&gt;/etc/kubernetes/pki/&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schedualer (&lt;code&gt;/etc/kubernetes/manifests/kube-scheduler.yaml&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. on &lt;code&gt;node&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh NODE
kube-bench run &lt;span class="nt"&gt;--targets&lt;/span&gt; node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  &lt;code&gt;kubelet&lt;/code&gt; is considering as node-level component
&lt;/h4&gt;

&lt;p&gt;it mainly checks &lt;code&gt;kubelet&lt;/code&gt; related configs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/var/lib/kubelet/config.yaml&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/etc/kubernetes/kubelet.conf&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;/etc/systemd/system/kubelet.service.d/10-kubeadm.conf&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;kubelet certificate location&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;anonymous auth&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;webhook authz&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;protecting &lt;code&gt;/var/lib/kubelet/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TLS bootstrapping config&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;client CA&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;permissions (644/600)&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice: need manually restart &lt;code&gt;kubelet&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  3. &lt;code&gt;etcd&lt;/code&gt; check
&lt;/h3&gt;

&lt;p&gt;we only focus on &lt;code&gt;kubeadm&lt;/code&gt; cluster ( for cloud, they will not expose etcd, and for external managed etcd cluster, ssh to the node)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubeadm&lt;/code&gt; will assign the &lt;code&gt;etcd&lt;/code&gt; to the &lt;code&gt;controlplane&lt;/code&gt; node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh CONTROLPLANE_NODE
kube-bench run &lt;span class="nt"&gt;--targets&lt;/span&gt; etcd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/etc/kubernetes/manifests/etcd.yaml&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Authentication enabled&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;--client-cert-auth=true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--peer-client-cert-auth=true&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Encryption enabled&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;--cert-file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--key-file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--peer-cert-file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--peer-key-file&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Proper paths&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/etc/kubernetes/pki/etcd/&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;notice: for &lt;code&gt;kubeadm&lt;/code&gt; cluster, &lt;code&gt;kubeadm&lt;/code&gt; will update the &lt;code&gt;/mainfests&lt;/code&gt; and then &lt;code&gt;kubelet&lt;/code&gt; will auto restart &lt;code&gt;etcd&lt;/code&gt;, there is no need to manually restart it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Notice:
&lt;/h2&gt;

&lt;p&gt;here we should notice command: &lt;code&gt;kube-bench run --targets node&lt;/code&gt; , for &lt;code&gt;kube-bench run --targets master&lt;/code&gt; or other targets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;master&lt;/code&gt; : API server, controller, etc —&lt;code&gt;kubelet&lt;/code&gt; watches the manifest files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;etcd&lt;/code&gt; : etcd services — &lt;code&gt;kubelet&lt;/code&gt; watches the manifest files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;policy&lt;/code&gt;: &lt;code&gt;kubectl&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the &lt;code&gt;kube-apiserver&lt;/code&gt;, &lt;code&gt;kube-controller-manager&lt;/code&gt;, &lt;code&gt;kube-scheduler&lt;/code&gt;, &lt;code&gt;etcd&lt;/code&gt; under &lt;code&gt;kubeadm&lt;/code&gt; cluster will managed by &lt;code&gt;kubeadm&lt;/code&gt;/&lt;code&gt;kubelet&lt;/code&gt; , the config file are under &lt;code&gt;/etc/kubernetes/manifests/*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And the &lt;code&gt;policy&lt;/code&gt; is control by &lt;code&gt;kubectl&lt;/code&gt;, so these we can just follow the recommendations which &lt;code&gt;kube-bench&lt;/code&gt; shows.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;How it runs&lt;/th&gt;
&lt;th&gt;Config change effect&lt;/th&gt;
&lt;th&gt;Restart needed?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;kube-apiserver&lt;/td&gt;
&lt;td&gt;Static pod&lt;/td&gt;
&lt;td&gt;Kubelet watches manifest&lt;/td&gt;
&lt;td&gt;No (auto restart)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kube-controller-manager&lt;/td&gt;
&lt;td&gt;Static pod&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kube-scheduler&lt;/td&gt;
&lt;td&gt;Static pod&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;etcd (kubeadm)&lt;/td&gt;
&lt;td&gt;Static pod&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;policies&lt;/td&gt;
&lt;td&gt;YAML API objects&lt;/td&gt;
&lt;td&gt;Apply with kubectl&lt;/td&gt;
&lt;td&gt;No restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kubelet&lt;/td&gt;
&lt;td&gt;systemd service&lt;/td&gt;
&lt;td&gt;Reads config only at startup&lt;/td&gt;
&lt;td&gt;Yes — manual restart&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;while for &lt;code&gt;kubelet&lt;/code&gt; related configs we need to find the &lt;code&gt;kubelet&lt;/code&gt; config file first, and then find the environment file location for fixing.&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;# find kubelet config file&lt;/span&gt;
systemctl status kubelet
&lt;span class="c"&gt;# find the env para settings file location&lt;/span&gt;
&lt;span class="c"&gt;# eg. the kubelet config is: /var/lib/kubelet/config.yaml, then inside it:&lt;/span&gt;
&lt;span class="nv"&gt;Environment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"&lt;/span&gt;
&lt;span class="c"&gt;# change the params in the corresponding file.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Do not directly fix the params in the &lt;code&gt;Environment:&lt;/code&gt; .&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Benchmark section&lt;/th&gt;
&lt;th&gt;Contains checks for&lt;/th&gt;
&lt;th&gt;kube-bench target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Master Node (1.x)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;API server, controller-manager, PKI, scheduler, etc.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;master&lt;/code&gt; or &lt;code&gt;controlplane&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Node (4.x)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kubelet, kubelet config, certificates, permissions&lt;/td&gt;
&lt;td&gt;&lt;code&gt;node&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;etcd (3.x)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;etcd service, certs, ports, flags&lt;/td&gt;
&lt;td&gt;&lt;code&gt;etcd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Policies (5.x)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PodSecurityPolicies (old), security policies&lt;/td&gt;
&lt;td&gt;&lt;code&gt;policies&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>cks</category>
      <category>kubernetes</category>
      <category>kubebench</category>
      <category>kubelet</category>
    </item>
    <item>
      <title>From one CSRF case to see handling third-party cookie blocking in browser</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Thu, 16 Oct 2025 17:22:44 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/from-one-csrf-case-to-see-handling-third-party-cookie-blocking-in-browser-1k2j</link>
      <guid>https://dev.to/cheedge_lee/from-one-csrf-case-to-see-handling-third-party-cookie-blocking-in-browser-1k2j</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Recently I am migrate my previous website for “&lt;a href="https://xn--einbrgerungstest-mzb.leeindeutschland.de" rel="noopener noreferrer"&gt;Lieben in Deutschland&lt;/a&gt;” test frontend to another cloud. And build for my new &lt;a href="https://www.leeindeutschland.de" rel="noopener noreferrer"&gt;backend demos&lt;/a&gt; website. But during these, I met with some issue with CSRF, and then I found this interesting topic of “third-party cookie blocking”. By this blog I want to record some steps of solving these issue.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  1. CSRF
&lt;/h1&gt;

&lt;p&gt;Let’s first have a quick review of CSRF attack and its solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.1 CSRF explaination
&lt;/h2&gt;

&lt;p&gt;We use CSRF as the case, which is the “Cross Site Request Forgery”, where the attacker can utilize the login user’s authenticated browser stored cookies to forge/trick a request and send to the server.&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%2Fvy3mtreq5z33yz5kwdug.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%2Fvy3mtreq5z33yz5kwdug.png" alt="csrf-0" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2.1 X-XSRF-Token
&lt;/h3&gt;

&lt;p&gt;As the cookie can be utilized to authenticate the malicious form submission, so we can add a token in both the cookie and the Headers.&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%2Fgz73zmkfyraynwkx20iv.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%2Fgz73zmkfyraynwkx20iv.png" alt="csrf-1" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even the cookies token stored cannot be check by the attacker, but it can still be used to send to the server, so what we actually need, is checking the token inside the Header with the session token on the server.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Session Link Always Works:&lt;/strong&gt; The &lt;strong&gt;Session Cookie&lt;/strong&gt; sent by the browser &lt;strong&gt;always matches&lt;/strong&gt; the server's session store (assuming the user is logged in). This cookie is what links the incoming request to the victim's authenticated session on the server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Token Mismatch is the Defense:&lt;/strong&gt; The server checks if the &lt;strong&gt;token inside the request body/header&lt;/strong&gt; matches the &lt;strong&gt;token stored &lt;em&gt;within&lt;/em&gt; the server-side session data&lt;/strong&gt; (which the Session Cookie points to).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1.2.2 .Net solution
&lt;/h3&gt;

&lt;p&gt;in .Net we use &lt;code&gt;Antiforgery&lt;/code&gt;, where we can add the Header name as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAntiforgery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HeaderName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"X-XSRF-TOKEN"&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;and for MVC we can directly use the middleware &lt;code&gt;ValidateAntiForgeryToken&lt;/code&gt;, and for web api, we can use the &lt;code&gt;IAntiforgery&lt;/code&gt; for the validation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CsrfDemoController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IAntiforgery&lt;/span&gt; &lt;span class="n"&gt;_antiforgery&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CsrfDemoController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAntiforgery&lt;/span&gt; &lt;span class="n"&gt;antiforgery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_antiforgery&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;antiforgery&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="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;GetCsrfToken&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_antiforgery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAndStoreTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;csrfToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestToken&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="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secure-action"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SecureAction&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// diff from MVC: Manual validation here, not using&lt;/span&gt;
            &lt;span class="c1"&gt;// [ValidateAntiForgeryToken]&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_antiforgery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateRequestAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CSRF token validated!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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;&lt;a href="http://ASP.NET" rel="noopener noreferrer"&gt;ASP.NET&lt;/a&gt; Core's &lt;code&gt;AddAntiforgery&lt;/code&gt; validation requires &lt;strong&gt;BOTH&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A cookie with a value&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A header/form field with a matching value&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It validates that they match.&lt;/p&gt;

&lt;h3&gt;
  
  
  1.2.3 JWT, etc
&lt;/h3&gt;

&lt;p&gt;Here we just one line notice that actually if we use JWT or other more secure way for login authentication, the CSRF will be avoid (even though the third party cookie as what we described in this article, as long as it not stored in third party cookie). But this is not our topic here.&lt;/p&gt;

&lt;h1&gt;
  
  
  2. Occurence
&lt;/h1&gt;

&lt;p&gt;How this related to the third party cookie blocking issue of browsers, for this I need to move to my recently re-deployment. In order to give a clear workflow, let’s focus on the main services, (So here we don’t mention API Gateway or other secure settings, also let DB alone). Use a simple and clean structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  2.1 Basic Architecture
&lt;/h2&gt;

&lt;p&gt;My new deployment is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;: &lt;em&gt;AWS Lambda (.Net 8)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;: &lt;em&gt;Netlify (React)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the backend code basically is like above .Net code, the frontend just some &lt;code&gt;fetch&lt;/code&gt; calls just like:&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="c1"&gt;// GET&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/CsrfDemo/token`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// very important for cookie&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// POST&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/api/CsrfDemo/secure-action`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X-XSRF-TOKEN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;csrfToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// antiforgery header, set in .net code&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// sends the cookie&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CSRF test from React&lt;/span&gt;&lt;span class="dl"&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;h2&gt;
  
  
  2.2 What happening?
&lt;/h2&gt;

&lt;p&gt;Before the deployment, I tested all the APIs locally, all APIs work well, CSRF API either.&lt;/p&gt;

&lt;p&gt;But after the deployment, I found CSRF API response in the browser will show me server error like:&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://tools.ietf.org/html/rfc9110#section-15.6.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An unexpected error occurred"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The required antiforgery cookie &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;.AspNetCore.Antiforgery&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; is not present."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"traceId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00-53a3ef57a071175d1c7dd256494f9d2c-1af0e8de357b09p7-00"&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;p&gt;it told me &lt;code&gt;.AspNetCore.Antiforgery&lt;/code&gt; is not shown in my cookie.&lt;/p&gt;

&lt;p&gt;That’s wired, because locally it test well, and even after deployment, when I use &lt;code&gt;curl&lt;/code&gt; to call CSRF API, it also works well and show &lt;code&gt;.AspNetCore.Antiforgery&lt;/code&gt; in cookies.&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;# 1. Get token and save cookies&lt;/span&gt;
curl &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; cookies.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Origin: https://myfrontend-addr"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://my-lambda-url/api/CsrfDemo/token

&lt;span class="c"&gt;# Check if cookies.txt has the antiforgery cookie&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;cookies.txt

&lt;span class="c"&gt;# 2. Use the saved cookies&lt;/span&gt;
curl &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; cookies.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &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/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-XSRF-TOKEN: &amp;lt;token-from-above&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Origin: https://myfrontend-addr"&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;'{"data": "test"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://my-lambda-url/api/CsrfDemo/secure-action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the &lt;code&gt;cookies.txt&lt;/code&gt; file show me:&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;# Netscape HTTP Cookie File&lt;/span&gt;
&lt;span class="c"&gt;# https://curl.se/docs/http-cookies.html&lt;/span&gt;
&lt;span class="c"&gt;# This file was generated by libcurl! Edit at your own risk.&lt;/span&gt;

&lt;span class="c"&gt;#HttpOnly_mylambda.lambda-url.on.aws    FALSE   /   TRUE    0   .AspNetCore.Antiforgery abcdefg1234567890&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it shows clearly the &lt;code&gt;.AspNetCore.Antiforgery&lt;/code&gt;, which include the CSRF token eg. “abcdefg1234567890”.&lt;/p&gt;

&lt;p&gt;So for now, as locally test successfully, my backend code logic is fine, and since curl works, this confirms my Lambda backend is configured correctly. The issue maybe &lt;strong&gt;browser-specific&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;browsers have stricter cookie policies than curl, especially for cross-origin requests.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2.3 Third Party cookies blocking (not store)
&lt;/h2&gt;

&lt;p&gt;Let’s see the browser request and response.&lt;/p&gt;

&lt;p&gt;By checking the Network, we found the for the GET token API response shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Wed, 15 Oct 2025 16:42:20 GMT&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json; charset=utf-8&lt;/span&gt;
&lt;span class="na"&gt;Content-Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;171&lt;/span&gt;
&lt;span class="na"&gt;Connection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keep-alive&lt;/span&gt;
&lt;span class="na"&gt;x-amzn-RequestId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1234567890&lt;/span&gt;
&lt;span class="na"&gt;access-control-allow-origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://my-frontend-url&lt;/span&gt;
&lt;span class="na"&gt;x-frame-options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SAMEORIGIN&lt;/span&gt;
&lt;span class="na"&gt;Set-Cookie&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.AspNetCore.Antiforgery=abcdefg1234567890; path=/; secure; samesite=none; httponly&lt;/span&gt;
&lt;span class="na"&gt;cache-control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no-cache, no-store&lt;/span&gt;
&lt;span class="na"&gt;X-Amzn-Trace-Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Root=abcd;Sampled=0;Lineage=1:80aab7f3:0&lt;/span&gt;
&lt;span class="na"&gt;pragma&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no-cache&lt;/span&gt;
&lt;span class="na"&gt;access-control-allow-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where we see &lt;code&gt;Set-Cookie&lt;/code&gt; Header and the &lt;code&gt;.AspNetCore.Antiforgery&lt;/code&gt; with CSRF token, eg. “abcdefg1234567890”.&lt;/p&gt;

&lt;p&gt;while as previous &lt;code&gt;React&lt;/code&gt; code we also see we already used:&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;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which should include the cookie, and browser then stored the cookie.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But when we check the cookies in the browser, we found empty, which means the browser hasn’t store the cookie.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is different from when we check the local deployment case, in local testing, after the get token, we will found the cookies are stored with the name &lt;code&gt;.AspNetCore.Antiforgery&lt;/code&gt; , which is same as what we see in above &lt;code&gt;curl&lt;/code&gt; cmd.&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%2F6gojhzx85u9sdoll9i65.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%2F6gojhzx85u9sdoll9i65.png" alt="screen shot" width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So here comes the &lt;strong&gt;third Party cookies blocking.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Actually the name third party cookies blocking actually is the &lt;strong&gt;browser will not store the cookies from third party.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Comparing with previous flow, now it looks like:&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%2Fwhojtgutv7byy274rypo.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%2Fwhojtgutv7byy274rypo.png" alt=" " width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is no cookie stored as it is third-party cookie, consequently, no cookie will be sent.&lt;/p&gt;

&lt;h1&gt;
  
  
  3. A simple Solution
&lt;/h1&gt;

&lt;p&gt;Now our situation is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GET request (works):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Browser sends GET to Lambda&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda responds with &lt;code&gt;Set-Cookie: .AspNetCore.Antiforgery=...&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Browser receives it BUT &lt;strong&gt;doesn't store it&lt;/strong&gt; (third-party cookie blocking)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;However, the response body still contains the token as JSON&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;POST request (fails):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Browser has no cookie stored (because it was blocked)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Browser sends POST with &lt;code&gt;X-XSRF-TOKEN&lt;/code&gt; header&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda checks: "Where's the cookie?" → Not found → Error&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Set-Cookie&lt;/code&gt; appears in response headers, but the browser &lt;strong&gt;silently refuses to store it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The reason caused this is about our .Net code, as we point out previous that ASP.NET Core's &lt;code&gt;AddAntiforgery&lt;/code&gt; validation requires &lt;strong&gt;BOTH&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A cookie with a value&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A header/form field with a matching value&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It validates that they match.&lt;/p&gt;

&lt;p&gt;But since the cookie isn't stored, validation will fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  3.1 Token comparison only from Headers
&lt;/h2&gt;

&lt;p&gt;As the &lt;code&gt;AddAntiforgery&lt;/code&gt; validate fails as the cookie are not stored, but we still want to validate the token, why not change the logic to only check the tokens from header manually, as the token in cookies will always same.&lt;/p&gt;

&lt;p&gt;So we &lt;strong&gt;extract the token from Header&lt;/strong&gt; &lt;code&gt;X-XSRF-TOKEN&lt;/code&gt; &lt;strong&gt;and compare with the backend stored token.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Therefore we have the method re-write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Mvc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;backend.CSRF.Controllers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ApiController&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/[controller]"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CsrfDemoController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ControllerBase&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// In-memory token storage, if focus on concurrency can use Redis to store&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&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;DateTime&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_tokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;_lock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;GetCsrfToken&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Generate random token by ourselves manully&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_tokens&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;csrfToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&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="nf"&gt;HttpPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secure-action"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;SecureAction&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;FromBody&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="kt"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Get token from header manully&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"X-XSRF-TOKEN"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;FirstOrDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;if&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="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Missing CSRF token"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="k"&gt;lock&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_lock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Check if token exists and is not expired&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Invalid or already-used CSRF token"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;_tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CSRF token expired"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;// Remove token (single-use)&lt;/span&gt;
                &lt;span class="n"&gt;_tokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"CSRF token validated!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now the token from GET method will be checked on the Network, Preview Tab, and in the POST method Request Headers, we can also find the &lt;code&gt;X-XSRF-TOKEN&lt;/code&gt; header.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In this article we use a simplified CSRF case to show this third party cookie blocking in Browser issue, and how to solve this problem via a simplified way. Actually, there are many more secure and better way to solve this issue, there are many article talk about it, eg. Microsoft Learn (&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/reference-third-party-cookies-spas" rel="noopener noreferrer"&gt;&lt;strong&gt;How to handle third-party cookie blocking in browsers&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;)&lt;/strong&gt;. For this article just a record of what issue I have met with during the re-deployment.&lt;/p&gt;

</description>
      <category>csrf</category>
      <category>webdev</category>
      <category>cookie</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>A simple but practical demo of Web Api in C#</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Sun, 12 Oct 2025 20:03:23 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/a-simple-but-practical-demo-of-web-api-in-c-13ad</link>
      <guid>https://dev.to/cheedge_lee/a-simple-but-practical-demo-of-web-api-in-c-13ad</guid>
      <description>&lt;p&gt;only show the basic steps, as for the other part, version control, CICD, Unit Test, just let it alone, we will focus on the basic structure of the project and the optimise of our code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;initial project&lt;/li&gt;
&lt;li&gt;architecture design CQRS, DDD, ES
&lt;/li&gt;
&lt;li&gt;EF and DTO mapping

&lt;ul&gt;
&lt;li&gt;DB design, Code First
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DIP &amp;amp; IoC&lt;/li&gt;
&lt;li&gt;CRUD implement&lt;/li&gt;
&lt;li&gt;Extra settings: log, api document&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Again, I should say it is just a simple demo for newbies to come familiar with these basic concepts, and it’s just some basics from a junior devs daily work. Therefore it is also suitable for someone who want to find a junior dev’s position. If you want more detailed info about each aspect, and deeper philosophy behind the scene, I really cannot help, I am just the screw of the machine, not the driver...&lt;/p&gt;

&lt;h2&gt;
  
  
  1 Initial Project
&lt;/h2&gt;

&lt;p&gt;By using Visual Studio to create an ASP.Net project, we can directly using its UI, select WebApi project, here we choose to use full web api instead of mini one. More conveniently, we can use command line &lt;code&gt;dotnet new webapi&lt;/code&gt; with name to create the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  2 Architecture Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2.1 CQRS
&lt;/h3&gt;

&lt;p&gt;The relative more modern architecture to build the WebApi project is CQRS (Command Query Responsibility Segregation), which you can just analogy it with the Read/Write separation in DB management, where command is the write operations (Create, Update, Delete), while query is stand for the read operation, therefore combining the write (create, update, delete) and the read query(retrieve) we can get all the operations of data.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 DDD
&lt;/h3&gt;

&lt;p&gt;DDD stands for Domain Driven Design, in more human-readable words, making the object readable to real world business.&lt;/p&gt;

&lt;p&gt;DDD is a separated layer, which should deal with the basic business logic, instead of handling the entity, DTOs mapping.&lt;/p&gt;

&lt;p&gt;On architecture aspect, we will see later, when we talk the relationship between about EF entity and DTO class. DDD domain model is an extra layer between EF entity and DTO class.&lt;/p&gt;

&lt;p&gt;Entity &amp;lt;--&amp;gt; Domain layer &amp;lt;--&amp;gt; DTO&lt;/p&gt;

&lt;p&gt;Repository/Reader/Writer -&amp;gt; Entity and Domain&lt;/p&gt;

&lt;p&gt;Handlers -&amp;gt; Domain and DTOs&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 ES
&lt;/h3&gt;

&lt;p&gt;Normally we keep a table to record the final results of the entity, for example, for each account we will have a current balance, this is the most common way we see, the table have only the final status of the entity. And this is a good way for read data, where we can directly get the current state. While on the other hand, a single final state which means squash all the previous states, when we try to roll back, there will be problems [1], to fix this, here comes Event-Sourcing, ES is a approach to record each operations so that the states can be build depend on each steps rather than the only final states.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[1] Notice: here we only want to explain the necessary of ES, for roll back or recovery, there are many other approaches without using ES. For example, for a traditional financial system, we always have the extra journal/ledger/audit tables in backoffice systems, which makes the system more robust, and secure. There are many materials, so we don’t expand it here, also I will have another note to discuss about it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here in order to further reduce the coupling, we will separate the event and the aggregate, therefore, we will have a separated &lt;code&gt;EventContext&lt;/code&gt; and the other Entity DbContext.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Architecture choice
&lt;/h3&gt;

&lt;p&gt;Feature specific&lt;/p&gt;

&lt;p&gt;DDD type&lt;/p&gt;

&lt;h2&gt;
  
  
  3 EF and DTO
&lt;/h2&gt;

&lt;p&gt;The basic functionality for a web api is to make the decoupling of the frontend and backend, and provide some standard entry for frontend to use to CRUD the data. And this decoupling approach we will see everywhere, and in some extent, all SOLID principles are related with decoupling more or less, in my view. Later we will see the DIP, which is the significant loose coupling approach. Here by using EF entity and the DTO class, we could realise that not mixing up the properties which the DB owns with the properties that can be directly used in CRUD, so that we will not expose all the properties, only provide the necessary entry.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Entity Framework
&lt;/h3&gt;

&lt;p&gt;For EF entity, we could using the well established Entity Framework (choose the compatible version with your .net version) for this data access process. Here we met with another choices: Code First Design vs DB First Design.&lt;/p&gt;

&lt;h4&gt;
  
  
  Code First Design vs DB First Design
&lt;/h4&gt;

&lt;p&gt;Here I don’t want to explain too much on these two concepts, if you like, there are many documents, blogs, tutorials discuss the difference and details about the two methods. We just list some common way to choose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DB already exist, or you prefer to write SQL use DB first.&lt;/li&gt;
&lt;li&gt;for new project, Code First is a good choice.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;we can choose the Code First approach, and we could either use&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attribute Annoation&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fluent Api (edit in &lt;code&gt;OnModelCreating&lt;/code&gt; method)&lt;/p&gt;

&lt;p&gt;for the table creating. And then using the &lt;code&gt;dotnet ef migration&lt;/code&gt; command for the initial, add, update, remove. It’s very convenient for users to manage the database.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3.2 DTO
&lt;/h3&gt;

&lt;p&gt;After automatically generating the entity, as we discuss above, it’s better to use another layer for apis to use the entity, which is the DTO. We can do all the operations on the DTO, and the DTO should include only the necessary properties which we want to operate on. Moreover, we also need to build the bridge between entity and the DTO class, and we could also build a separated Mapper class by using the automapper or you can crated your own automapper via reflection/expression tree, (if you are interesting in this, pls check my another blog [here]).&lt;/p&gt;

&lt;h4&gt;
  
  
  Read/Write DTO
&lt;/h4&gt;

&lt;p&gt;One step further, if we use the traditional CRUD methods, we could also separate the DTO into Read/Write DTOs, which means when we do the Get method, we can use the Read DTO, where only stores the properties Get method needs; while when we use Create/Update/Delete methods, we will change the data, some of the immutable properties should not be include, therefore we will choose the Write DTO.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.3 Domain Model
&lt;/h3&gt;

&lt;p&gt;As we discussed in DDD section, a concrete example for DDD implementation is the Domain Model layer between EF Entity and the DTO. Rather than including simple properties, Domain Model are more linking with business entity, aggregate, logic, further more, it also has the behaviours.&lt;/p&gt;

&lt;p&gt;For example, the&lt;/p&gt;

&lt;p&gt;Instead of directly building the mapper between entity and DTO, we break it into two steps:&lt;/p&gt;

&lt;p&gt;EF Entity &amp;lt;--&amp;gt; Domain Model&lt;/p&gt;

&lt;p&gt;Domain Model &amp;lt;--&amp;gt; DTO&lt;/p&gt;

&lt;p&gt;For each steps build the mapper and then we can still use the DTO for all operations.&lt;/p&gt;

&lt;p&gt;If take it more rigorous, we should say there is another mapping,&lt;/p&gt;

&lt;p&gt;DbContext &amp;lt;--&amp;gt; EF Entity&lt;/p&gt;

&lt;p&gt;which we already discussed in above section &lt;strong&gt;Code First Design vs DB First Design&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice: here we only separate the Entity which will be exposed through a public API or send them over the wire, which means the events entity should be internal, as we don’t want those events to expose as a public API. These &lt;strong&gt;internal-only events&lt;/strong&gt; (for projections, logging, integration handlers) don’t need DTOs, so they work directly with our &lt;code&gt;IDomainEvent&lt;/code&gt; types and the &lt;code&gt;EventEntity&lt;/code&gt; persistence model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4 DIP &amp;amp; IoC (DI)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 DIP
&lt;/h3&gt;

&lt;p&gt;As we discussing in above sections, in order to loose coupling so that making code more flexible for changing and extension. Additionally, it also easy for Unit Test. Still, there are many books, tutorials, blogs to explain the necessary, advantage, and the details of DIP, so we will not discuss it deeply here ([here] is also an unfinished tutorial for some basic info). What I want to simply show is the two basic approaches for implementations in web api.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.1.1 Architecture Level
&lt;/h4&gt;

&lt;p&gt;On architecture level decoupling, we will extract the corresponding interface of each layer, for example, for the Business Logic Layer(BLL) we can extract a separated interface layer for it (as IBLL), so that we can reduce the coupling.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.1.2 Component (class) Level
&lt;/h4&gt;

&lt;p&gt;On component level decoupling, instead of directly using the instance of the class, we will use the interface during the declaration.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 IoC (DI)
&lt;/h3&gt;

&lt;p&gt;And we will also discuss another related concept IoC and DI, here DI stands for dependency injection. More accurately, DI is one concrete form of IoC. As the name suggests, IoC is to give the control to the other — the invoke side.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.2.1 Architecture Level
&lt;/h4&gt;

&lt;p&gt;On Architecture level, IoC mainly reflects on the usage of container. When we initial the web api, we could found the built-in container in the main entry (&lt;code&gt;Program.cs&lt;/code&gt;). Individually, we can also use other containers, most common containers as: &lt;code&gt;Castle.Windsor&lt;/code&gt;, &lt;code&gt;Unity&lt;/code&gt;, &lt;code&gt;Autofac&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  4.2.2 Component (class) Level
&lt;/h4&gt;

&lt;p&gt;On component (class) level, we could see the introducing of interface in method parameters. Instead of directly using the class as the parameter, we will use the interface during the parameters passing. By this, we will give the control to the invoking side.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 CRUD
&lt;/h2&gt;

&lt;p&gt;Till now all the entity, model, dto has been created, then we can build the reader/writer/handlers&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Pure Reader/Writer
&lt;/h3&gt;

&lt;p&gt;Here by "pure" I mean these readers/writers are purely responsible only for communicating with database, so the operations here are clear CRUD ops. And the logic for readers/writers should be complete and simple for basic ops with DB.&lt;/p&gt;

&lt;h4&gt;
  
  
  Readers Methods
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;GetEntityById&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GetEntitiesByField&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GetEntitiesByMultiFields&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;GetAll&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Writers Methods
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AddNewEntity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;UpdateEntity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DeleteEntity&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SaveChanges&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By this, we can not only make our Unit Tests easier, but also separate the Business Logic and the Data Access more clearly, which means we will put all business logics into the Query/Command Handlers and only need to do unit test on the handlers.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2 Repository ( DDD+ES )
&lt;/h3&gt;

&lt;p&gt;By using DDD with ES, we normally not directly deal with the CRUD operations, instead we will accumulate the events to get the final status. Therefore we will put the read and write operations into one repository, and there are two basic ops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;SaveNewEvent&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ProjectEvent/AppendEvent&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;GetCurrentState&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;SaveNewEvent to the event table, at the same time update the existing or new created object table (eg. AccountTbl). And GetCurrentSate of current object table, which is the get operation.&lt;/p&gt;

&lt;p&gt;Therefore, now under this pattern the workflow looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Controller get/process request&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handler assemble the event from passed in request&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repository use Event Domain Model to reconstruct the command and update states.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>cqrs</category>
      <category>ioc</category>
      <category>ddd</category>
    </item>
    <item>
      <title>Configuring Your Home Cluster Network: A Complete Guide</title>
      <dc:creator>Cheedge Lee</dc:creator>
      <pubDate>Sun, 02 Mar 2025 20:19:17 +0000</pubDate>
      <link>https://dev.to/cheedge_lee/configuring-your-home-cluster-network-a-complete-guide-1bn7</link>
      <guid>https://dev.to/cheedge_lee/configuring-your-home-cluster-network-a-complete-guide-1bn7</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;It’s a renovated &lt;a href="https://blog.csdn.net/li_6698230?type=blog" rel="noopener noreferrer"&gt;note&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nowadays many of us will enjoy the cloud cluster rather than build a self managed cluster, as it’s less management, high availability, more secure, pay-as-you-go, and all the advantages you can think of the cloud computing. However, if you accidentally own several old computers, and don’t want to sell/transfer them and don’t know how to deal with them, a home-managed cluster will be a good choice. there is a lot of fun of a self managed cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;Let’s define our architecture here: 4 nodes: 1 master/worker, 3 worker&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%2Fh1oa7bbyrfsdj4jjccsa.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%2Fh1oa7bbyrfsdj4jjccsa.png" alt="Image description" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. master node
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;eth0: connect with institute cable&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;public&lt;/strong&gt; &lt;strong&gt;interface&lt;/strong&gt;, using DHCP&lt;/li&gt;
&lt;li&gt;internet download, update&lt;/li&gt;
&lt;li&gt;users can access it: ssh/scp&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;eth1: connect with worker ndoes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;private interface&lt;/strong&gt;, using static IP&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;communicate other code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ssh/scp&lt;/li&gt;
&lt;li&gt;data transfer&lt;/li&gt;
&lt;li&gt;parallel communicate&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  config network interface eth0 and eth1
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Configure public interface (assumes DHCP from institute network)&lt;/span&gt;
cat &amp;gt; /etc/sysconfig/network-scripts/ifcfg-eth0 &amp;lt;&amp;lt; EOF
&lt;span class="nc"&gt;TYPE&lt;/span&gt;=Ethernet
DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes
&lt;span class="c"&gt;# Request static IP from our institute's DHCP server if possible&lt;/span&gt;
&lt;span class="c"&gt;# This makes routing more reliable&lt;/span&gt;
DHCP_CLIENT_ID=cluster-master
EOF

&lt;span class="c"&gt;# Configure private cluster network&lt;/span&gt;
cat &amp;gt; /etc/sysconfig/network-scripts/ifcfg-eth1 &amp;lt;&amp;lt; EOF
&lt;span class="nc"&gt;TYPE&lt;/span&gt;=Ethernet
DEVICE=eth1
BOOTPROTO=static
&lt;span class="nc"&gt;IPADDR&lt;/span&gt;=192.168.10.1
NETMASK=255.255.255.0
ONBOOT=yes
EOF

&lt;span class="c"&gt;# Apply new network configuration&lt;/span&gt;
systemctl restart network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  NAT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable IP forwarding persistently&lt;/span&gt;
&lt;span class="nc"&gt;echo&lt;/span&gt; "net.ipv4.ip_forward = 1" &amp;gt;&amp;gt; /etc/sysctl.conf

&lt;span class="c"&gt;# Enable connection tracking timeout optimization for HPC workloads&lt;/span&gt;
&lt;span class="nc"&gt;echo&lt;/span&gt; "net.netfilter.nf_conntrack_tcp_timeout_established = 86400" &amp;gt;&amp;gt; /etc/sysctl.conf
&lt;span class="nc"&gt;echo&lt;/span&gt; "net.netfilter.nf_conntrack_max = 131072" &amp;gt;&amp;gt; /etc/sysctl.conf

&lt;span class="c"&gt;# Apply sysctl changes&lt;/span&gt;
sysctl -p

&lt;span class="c"&gt;# Set up NAT with higher connection limits&lt;/span&gt;
iptables -t nat -A POSTROUTING -o eth0 -s 192.168.10.0/24 -j MASQUERADE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the last command is important, as it will contribute to the return traffic. We will explain later.&lt;/p&gt;

&lt;h3&gt;
  
  
  setup the firewall
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clear existing rules&lt;/span&gt;
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X

&lt;span class="c"&gt;# Set default policies&lt;/span&gt;
iptables -P &lt;span class="ss"&gt;INPUT&lt;/span&gt; DROP
iptables -P FORWARD DROP  &lt;span class="c"&gt;# We'll explain this choice&lt;/span&gt;
iptables -P &lt;span class="ss"&gt;OUTPUT&lt;/span&gt; ACCEPT

&lt;span class="c"&gt;# Allow loopback&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -i lo -j ACCEPT

&lt;span class="c"&gt;# Allow established and related connections&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT

&lt;span class="c"&gt;# Allow SSH from institute network&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -i eth0 -p tcp --dport 22 -j ACCEPT

&lt;span class="c"&gt;# Allow all traffic from the cluster's private network&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -i eth1 -s 192.168.10.0/24 -j ACCEPT

&lt;span class="c"&gt;# Allow forwarding from cluster to internet&lt;/span&gt;
iptables -A FORWARD -i eth1 -s 192.168.10.0/24 -o eth0 -j ACCEPT

&lt;span class="c"&gt;# Allow HTTP/HTTPS for package downloads&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -i eth0 -p tcp --dport 80 -j ACCEPT
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -i eth0 -p tcp --dport 443 -j ACCEPT

&lt;span class="c"&gt;# Set up local package caching repository later (optional)&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -i eth1 -p tcp --dport 80 -j ACCEPT

&lt;span class="c"&gt;# Save iptables rules&lt;/span&gt;
iptables-save &amp;gt; /etc/sysconfig/iptables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I set the default FORWARD policy to DROP for security reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It prevents unauthorized traffic from traversing the master node&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It creates a default-deny stance, where only explicitly allowed traffic passes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It prevents potential lateral movement if one node is compromised&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. worker nodes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Worker node 1 (192.168.10.2)&lt;/span&gt;
cat &amp;gt; /etc/sysconfig/network-scripts/ifcfg-eth0 &amp;lt;&amp;lt; EOF
&lt;span class="nc"&gt;TYPE&lt;/span&gt;=Ethernet
DEVICE=eth0
BOOTPROTO=static
&lt;span class="nc"&gt;IPADDR&lt;/span&gt;=192.168.10.2
NETMASK=255.255.255.0
GATEWAY=192.168.10.1
DNS1=8.8.8.8
ONBOOT=yes
EOF

&lt;span class="c"&gt;# here GATEWAY will auto generate the route rule in table&lt;/span&gt;

&lt;span class="c"&gt;# Worker node 2 (192.168.10.3)&lt;/span&gt;
&lt;span class="c"&gt;# Change IPADDR=192.168.10.3 on the second worker node&lt;/span&gt;

&lt;span class="c"&gt;# Worker node 3 (192.168.10.4)&lt;/span&gt;
&lt;span class="c"&gt;# Change IPADDR=192.168.10.4 on the third worker node&lt;/span&gt;

&lt;span class="c"&gt;# Restart network service on each worker node&lt;/span&gt;
systemctl restart network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Routing Configuration for Worker Nodes
&lt;/h3&gt;

&lt;p&gt;As we use the master node &lt;code&gt;eth1&lt;/code&gt; (&lt;code&gt;192.168.10.1&lt;/code&gt;) as the gateway for work nodes (&lt;code&gt;GATEWAY=192.168.10.1&lt;/code&gt;), above setting creates a default route on each worker node that sends all traffic not destined for the local network (192.168.10.0/24) to the master node (192.168.10.1).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; route -n
&lt;span class="c"&gt;# see the result: (send all traffic (0.0.0.0/0) to gateway 192.168.10.1)&lt;/span&gt;
0.0.0.0         192.168.10.1      0.0.0.0         UG    0      0        0 eth0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manual Command
&lt;/h3&gt;

&lt;p&gt;Using manual command can also achieve the same result.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;route &lt;span class="ss"&gt;add&lt;/span&gt; -net 0.0.0.0 gw 192.168.10.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This manually adds a default route to the current routing table. It has the same immediate effect as the configuration file setting, but it's temporary and will be lost after a reboot or network service restart.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The difference is primarily in persistence and when the configuration happens. Using the network configuration file is the standard way to set up permanent routes in CentOS/RHEL systems.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Fire wall
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clear existing rules&lt;/span&gt;
iptables -F
iptables -X

&lt;span class="c"&gt;# Set default policies&lt;/span&gt;
iptables -P &lt;span class="ss"&gt;INPUT&lt;/span&gt; DROP
iptables -P FORWARD DROP
iptables -P &lt;span class="ss"&gt;OUTPUT&lt;/span&gt; ACCEPT

&lt;span class="c"&gt;# Allow loopback&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -i lo -j ACCEPT

&lt;span class="c"&gt;# Allow established connections&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -m state --state ESTABLISHED,RELATED -j ACCEPT

&lt;span class="c"&gt;# Allow SSH from cluster nodes only&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -p tcp --dport 22 -s 192.168.10.0/24 -j ACCEPT

&lt;span class="c"&gt;# HPC/MPI Communication - comprehensive approach&lt;/span&gt;
&lt;span class="c"&gt;# Allow all TCP/UDP between cluster nodes for parallel computing&lt;/span&gt;
&lt;span class="c"&gt;# this is optinal if you don't want do parallel computing&lt;/span&gt;
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -s 192.168.10.0/24 -p tcp -j ACCEPT
iptables -A &lt;span class="ss"&gt;INPUT&lt;/span&gt; -s 192.168.10.0/24 -p udp -j ACCEPT

&lt;span class="c"&gt;# Save iptables rules&lt;/span&gt;
iptables-save &amp;gt; /etc/sysconfig/iptables
systemctl enable iptables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Package management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  3.1 Local yum Repo
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;because the worker node will not have the access to internet, we keep them inside the private, therefore, for package installation, updating, we need to find a way to resolve these.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;we want to specify the packages in our yum repo&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;so here we build a local yum repo&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# On master node&lt;/span&gt;
&lt;span class="c"&gt;# Install required packages&lt;/span&gt;
yum install -y createrepo nginx

&lt;span class="c"&gt;# Create repository directory&lt;/span&gt;
mkdir -p /var/www/html/centos-repo

&lt;span class="c"&gt;# Configure Nginx&lt;/span&gt;
cat &amp;gt; /etc/nginx/conf.d/repo.conf &amp;lt;&amp;lt; EOF
&lt;span class="nc"&gt;server&lt;/span&gt; {
    &lt;span class="nc"&gt;listen&lt;/span&gt; 80;
    &lt;span class="nc"&gt;server_name&lt;/span&gt; _;
    root /var/www/html;

    &lt;span class="nc"&gt;location&lt;/span&gt; / {
        autoindex on;
    &lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
EOF

&lt;span class="c"&gt;# Start and enable Nginx&lt;/span&gt;
systemctl enable nginx
systemctl start nginx

&lt;span class="c"&gt;# Download packages to repository&lt;/span&gt;
yum install -y yum-utils
repotrack -p /var/www/html/centos-repo &amp;lt;package-name&amp;gt; 
&lt;span class="c"&gt;# Repeat for packages we need&lt;/span&gt;

&lt;span class="c"&gt;# Create repository metadata&lt;/span&gt;
createrepo /var/www/html/centos-repo

&lt;span class="c"&gt;# Configure worker nodes to use this repository&lt;/span&gt;
cat &amp;gt; /etc/yum.repos.d/cluster-local.repo &amp;lt;&amp;lt; EOF
&lt;span class="err"&gt;[&lt;/span&gt;cluster-local]
&lt;span class="nc"&gt;name&lt;/span&gt;=Cluster Local Repository
baseurl=http://192.168.10.1/centos-repo
enabled=1
gpgcheck=0
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.2 Optional for package management
&lt;/h3&gt;

&lt;p&gt;In order to realise&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;worker node package update&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;self-managed packages&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;we can also use scp to transfer package from master node.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# On master node, download and transfer RPM&lt;/span&gt;
yum install -y yum-utils
yumdownloader &amp;lt;package-name&amp;gt;
scp &amp;lt;package-name&amp;gt;.rpm 192.168.10.1:/tmp/

&lt;span class="c"&gt;# On worker node&lt;/span&gt;
sudo rpm -ivh /tmp/&amp;lt;package-name&amp;gt;.rpm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.3 directly route worker node to internet
&lt;/h3&gt;

&lt;p&gt;If we don’t need high security, we can also open the private cluster to public internet. Which will configure the router table and we don’t discuss here.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Other Installations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ssh configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# On master node, generate SSH key&lt;/span&gt;
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""

&lt;span class="c"&gt;# Copy the key to all nodes (including itself)&lt;/span&gt;
for i &lt;span class="ss"&gt;in&lt;/span&gt; {1..4}; do
  ssh-copy-id -i ~/.ssh/id_rsa.pub 10.10.10.$i
done

&lt;span class="c"&gt;# Do the same on each worker node to allow any-to-any communication&lt;/span&gt;
&lt;span class="c"&gt;# (Run similar commands on each worker node)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Network Monitor &amp;amp; iptables Log
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install tools&lt;/span&gt;
yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; tcpdump nmap iftop

&lt;span class="c"&gt;# Set up automatic monitoring with fail2ban to prevent brute force attacks&lt;/span&gt;
yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; fail2ban
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/fail2ban/jail.local &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;
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure
maxretry = 5
bantime = 3600
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Start and enable fail2ban&lt;/span&gt;
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;fail2ban
systemctl start fail2ban

&lt;span class="c"&gt;# Add logging rules before the final DROP rules&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-j&lt;/span&gt; LOG &lt;span class="nt"&gt;--log-prefix&lt;/span&gt; &lt;span class="s2"&gt;"IPTables-Input-Dropped: "&lt;/span&gt; &lt;span class="nt"&gt;--log-level&lt;/span&gt; 4
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; FORWARD &lt;span class="nt"&gt;-j&lt;/span&gt; LOG &lt;span class="nt"&gt;--log-prefix&lt;/span&gt; &lt;span class="s2"&gt;"IPTables-Forward-Dropped: "&lt;/span&gt; &lt;span class="nt"&gt;--log-level&lt;/span&gt; 4

&lt;span class="c"&gt;# Save iptables rules&lt;/span&gt;
iptables-save &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/sysconfig/iptables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Optional Parallel Computing Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# On all nodes (master and workers)&lt;/span&gt;
&lt;span class="c"&gt;# Install OpenMPI&lt;/span&gt;
yum install -y openmpi openmpi-devel

&lt;span class="c"&gt;# Configure environment in /etc/profile.d/&lt;/span&gt;
cat &amp;gt; /etc/profile.d/mpi.sh &amp;lt;&amp;lt; EOF
export PATH=\$PATH:/usr/lib64/openmpi/bin
export LD_LIBRARY_PATH=\$LD_LIBRARY_PATH:/usr/lib64/openmpi/lib
EOF

&lt;span class="c"&gt;# Source the new environment&lt;/span&gt;
source /etc/profile.d/mpi.sh

&lt;span class="c"&gt;# Test MPI communication&lt;/span&gt;
&lt;span class="c"&gt;# Create a hostfile&lt;/span&gt;
cat &amp;gt; /home/username/hostfile &amp;lt;&amp;lt; EOF
192.168.10.1 slots=128
192.168.10.2 slots=128
192.168.10.3 slots=128
192.168.10.4 slots=128
EOF

&lt;span class="c"&gt;# Run a simple MPI test&lt;/span&gt;
mpirun -np 4 --hostfile /home/username/hostfile &lt;span class="ss"&gt;hostname&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Torque/PBS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Torque on master node&lt;/span&gt;
yum install -y torque-server torque-scheduler torque-client

&lt;span class="c"&gt;# Configure server nodes file&lt;/span&gt;
cat &amp;gt; /var/torque/server_priv/nodes &amp;lt;&amp;lt; EOF
192.168.10.1 np=128
192.168.10.2 np=128
192.168.10.3 np=128
192.168.10.4 np=128
EOF

&lt;span class="c"&gt;# Start Torque server&lt;/span&gt;
systemctl enable pbs_server
systemctl start pbs_server

&lt;span class="c"&gt;# Install Torque on worker nodes&lt;/span&gt;
for i &lt;span class="ss"&gt;in&lt;/span&gt; {2..4}; do
  ssh 192.168.10.$i "yum install -y torque-mom torque-client; systemctl enable pbs_mom; systemctl start pbs_mom"
done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Traffic Flow:
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Forward Chain Traffic Flow in &lt;em&gt;Both&lt;/em&gt; Directions
&lt;/h2&gt;

&lt;p&gt;When we create the forward chain, &lt;code&gt;iptables -A FORWARD -i eth1 -s 192.168.10.0/24 -o eth0 -j ACCEPT&lt;/code&gt;, this command let the traffic come into &lt;code&gt;eth1&lt;/code&gt; can be forward to &lt;code&gt;eth0&lt;/code&gt;, which means traffic from worker nodes to master nodes, and master nodes forward it to institute internet. Here comes the question, &lt;strong&gt;where is the backward flow?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;iptables -A FORWARD -i eth1 -s 192.168.10.0/24 -o eth0 -j ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s see how the traffic flows first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outbound Traffic (Worker → Internet)
&lt;/h3&gt;

&lt;p&gt;The rule above allows packets to travel from the worker nodes (coming in on &lt;code&gt;eth1&lt;/code&gt;) to be forwarded out to the institute network (through &lt;code&gt;eth0&lt;/code&gt;). This handles the first half of any connection, which is the outbound request.&lt;/p&gt;

&lt;h3&gt;
  
  
  Return Traffic (Internet → Worker)
&lt;/h3&gt;

&lt;p&gt;For the return traffic, typically we will think what we need as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;iptables -A FORWARD -i eth0 -d 192.168.10.0/24 -o eth1 -j ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, if we look closely at the original configuration, here is the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule is handling the return traffic, because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;When a worker node initiates a connection, the outbound packet creates an entry in the connection tracking table (&lt;strong&gt;create a ESTABLISHED connection&lt;/strong&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any returning packets associated with that connection are marked as ESTABLISHED&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The rule above allows all ESTABLISHED connections through, regardless of interface&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is more secure than explicitly allowing all traffic from  &lt;code&gt;eth0&lt;/code&gt; to  &lt;code&gt;eth1&lt;/code&gt;, because it only permits return traffic for connections that were initiated from inside our cluster.&lt;/p&gt;

&lt;p&gt;If this state tracking rule wasn't present, we would absolutely need to the backward traffic rule. Without either approach, connections would work one-way only, which means worker nodes could send requests, but never receive response…&lt;/p&gt;

&lt;h3&gt;
  
  
  Connection Flow
&lt;/h3&gt;

&lt;p&gt;Let's trace a web request from a worker node:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Worker (192.168.10.2) tries to access &lt;a href="http://google.com" rel="noopener noreferrer"&gt;google.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Packet travels: Worker → Master's &lt;code&gt;eth1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Master checks &lt;strong&gt;FORWARD&lt;/strong&gt; chain, matches &lt;code&gt;-i eth1 -s 192.168.10.0/24 -o eth0&lt;/code&gt; rule&lt;/li&gt;
&lt;li&gt;Master performs &lt;strong&gt;NAT&lt;/strong&gt;, &lt;em&gt;changing source IP to its own public IP&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Packet leaves through &lt;code&gt;eth0&lt;/code&gt; to institute network&lt;/li&gt;
&lt;li&gt;Google responds to master's public IP&lt;/li&gt;
&lt;li&gt;Packet arrives at master's &lt;code&gt;eth0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Master checks connection tracking table, sees this is a response&lt;/li&gt;
&lt;li&gt;Packet is marked as &lt;strong&gt;ESTABLISHED&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Master checks &lt;strong&gt;FORWARD&lt;/strong&gt; chain, matches the ESTABLISHED rule&lt;/li&gt;
&lt;li&gt;Master performs reverse NAT, changing destination to worker's IP&lt;/li&gt;
&lt;li&gt;Packet leaves through eth1 to worker
&lt;/li&gt;
&lt;li&gt;Worker receives response&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  More details
&lt;/h2&gt;

&lt;p&gt;more details can check the post before &lt;a href="https://blog.csdn.net/li_6698230?type=blog" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
