<?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: Samer Aburabie</title>
    <description>The latest articles on DEV Community by Samer Aburabie (@sameraburabie).</description>
    <link>https://dev.to/sameraburabie</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3693872%2Fa71ed4d0-bf3e-4fce-bb22-ccceb73d2abf.png</url>
      <title>DEV Community: Samer Aburabie</title>
      <link>https://dev.to/sameraburabie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sameraburabie"/>
    <language>en</language>
    <item>
      <title>Creating a Kubernetes scoped kubeconfig by hand is 15 steps of pain. It shouldn't be.</title>
      <dc:creator>Samer Aburabie</dc:creator>
      <pubDate>Sat, 27 Jun 2026 20:54:06 +0000</pubDate>
      <link>https://dev.to/sameraburabie/creating-a-kubernetes-scoped-kubeconfig-by-hand-is-15-steps-of-pain-it-shouldnt-be-27ig</link>
      <guid>https://dev.to/sameraburabie/creating-a-kubernetes-scoped-kubeconfig-by-hand-is-15-steps-of-pain-it-shouldnt-be-27ig</guid>
      <description>&lt;p&gt;Handing someone least-privilege access to a Kubernetes cluster means assembling a scoped kubeconfig by hand — ServiceAccount, Role, RoleBinding, token, CA, YAML. Here's the full gauntlet, and how Kubexer collapses it.&lt;/p&gt;

&lt;p&gt;A teammate needs to deploy to one namespace. A CI bot needs read-only access to pods and logs. A contractor needs to poke at &lt;code&gt;team-alpha&lt;/code&gt; and nothing else.&lt;/p&gt;

&lt;p&gt;The right answer is never "here, use my admin kubeconfig." The right answer is a &lt;strong&gt;scoped kubeconfig&lt;/strong&gt; — least privilege, narrow blast radius, easy to revoke. The problem is that creating one by hand is a genuinely tedious ritual that almost nobody enjoys, and that friction is exactly why over-privileged credentials keep leaking into Slack threads and CI secrets.&lt;/p&gt;

&lt;p&gt;Let me walk through the full manual process, because the number of moving parts is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "scoped" actually means
&lt;/h2&gt;

&lt;p&gt;A scoped kubeconfig is just a normal kubeconfig whose identity maps to a &lt;strong&gt;ServiceAccount&lt;/strong&gt; that's been granted a tightly-defined &lt;strong&gt;Role&lt;/strong&gt;. Instead of carrying your broad user credentials, it carries a token that can only do the specific verbs on the specific resources in the specific namespace you allowed.&lt;/p&gt;

&lt;p&gt;To build one, you need five things to exist and agree with each other: a ServiceAccount, a Role, a RoleBinding, a token, and the cluster's CA + API endpoint. Then you assemble them into a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The manual gauntlet
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create the ServiceAccount
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create serviceaccount deploy-bot &lt;span class="nt"&gt;-n&lt;/span&gt; team-alpha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Write a Role with exactly the permissions you want
&lt;/h3&gt;

&lt;p&gt;This is the part you actually have to &lt;em&gt;think&lt;/em&gt; about — every extra verb is extra blast radius.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deploy-bot-role
  namespace: team-alpha
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "update", "patch"]
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Bind the ServiceAccount to the Role
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create rolebinding deploy-bot-binding &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deploy-bot-role &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--serviceaccount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;team-alpha:deploy-bot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; team-alpha
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Mint a token — and here's where 1.24 made it worse
&lt;/h3&gt;

&lt;p&gt;Before Kubernetes 1.24, a ServiceAccount automatically got a long-lived token Secret you could just read. Since 1.24 that auto-generation is gone (&lt;code&gt;LegacyServiceAccountTokenNoAutoGeneration&lt;/code&gt;). So now you mint one explicitly:&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;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl create token deploy-bot &lt;span class="nt"&gt;-n&lt;/span&gt; team-alpha &lt;span class="nt"&gt;--duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24h&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;kubectl create token&lt;/code&gt; returns a &lt;strong&gt;time-bound&lt;/strong&gt; token. Great for security, annoying for a credential you wanted to hand off once. If you need something longer-lived, you're back to hand-crafting a &lt;code&gt;kubernetes.io/service-account-token&lt;/code&gt; Secret — which reintroduces exactly the long-lived-credential risk the 1.24 change was trying to kill. Pick your poison.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Extract the cluster CA and API endpoint
&lt;/h3&gt;

&lt;p&gt;The new kubeconfig has to trust the cluster, so you pull the CA and server out of your current config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CLUSTER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl config view &lt;span class="nt"&gt;--minify&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.clusters[0].name}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl config view &lt;span class="nt"&gt;--minify&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.clusters[0].cluster.server}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
kubectl config view &lt;span class="nt"&gt;--raw&lt;/span&gt; &lt;span class="nt"&gt;--minify&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.clusters[0].cluster.certificate-authority-data}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/ca.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes — you base64-&lt;em&gt;decode&lt;/em&gt; the CA into a temp file, because the next command wants a file path, not inline data. (And then you get to remember to delete that temp file.)&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Assemble the kubeconfig, one command at a time
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KCFG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./deploy-bot.kubeconfig

&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$KCFG&lt;/span&gt; kubectl config set-cluster &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLUSTER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--server&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SERVER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--certificate-authority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/ca.crt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--embed-certs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true

&lt;/span&gt;&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$KCFG&lt;/span&gt; kubectl config set-credentials deploy-bot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$KCFG&lt;/span&gt; kubectl config set-context deploy-bot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLUSTER_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deploy-bot &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;team-alpha

&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$KCFG&lt;/span&gt; kubectl config use-context deploy-bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Test it before you trust it
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./deploy-bot.kubeconfig kubectl auth can-i list pods &lt;span class="nt"&gt;-n&lt;/span&gt; team-alpha   &lt;span class="c"&gt;# yes&lt;/span&gt;
&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./deploy-bot.kubeconfig kubectl auth can-i delete secrets &lt;span class="nt"&gt;-n&lt;/span&gt; team-alpha  &lt;span class="c"&gt;# no, good&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then clean up the temp CA, and distribute the file securely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Count the ways this goes wrong
&lt;/h2&gt;

&lt;p&gt;That's roughly fifteen discrete actions across four object kinds, two base64 operations, several jsonpath incantations, and a hand-assembled file. Every step is a place to slip:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Over-scoping&lt;/strong&gt; — it's faster to grant &lt;code&gt;"*"&lt;/code&gt; verbs than to enumerate them, so people do, and least privilege quietly dies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong CA or server&lt;/strong&gt; — and the config silently fails to connect with a TLS error that tells you nothing useful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token expiry surprises&lt;/strong&gt; — the 24h token works in your test and dies in production overnight.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaked temp files&lt;/strong&gt; — that &lt;code&gt;/tmp/ca.crt&lt;/code&gt; and the token in your shell history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No record&lt;/strong&gt; — three weeks later, nobody remembers which SAs and Roles exist or why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is hard, exactly. It's just &lt;em&gt;fiddly&lt;/em&gt;, and fiddly-but-security-critical is the worst combination, because the path of least resistance is the insecure one.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Kubexer collapses it
&lt;/h2&gt;

&lt;p&gt;This is the workflow I wanted to stop doing by hand, so I built it into &lt;strong&gt;Kubexer&lt;/strong&gt;, a privacy-first Kubernetes desktop IDE.&lt;/p&gt;

&lt;p&gt;Instead of the gauntlet above, you describe the &lt;em&gt;intent&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick the namespace.&lt;/li&gt;
&lt;li&gt;Pick the resources and verbs you want to allow (or start from a least-privilege template like "read-only" or "deploy-only").&lt;/li&gt;
&lt;li&gt;Set a token lifetime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubexer creates the ServiceAccount, the Role, and the RoleBinding, mints the token, pulls the cluster CA and endpoint straight from your active context, and assembles a ready-to-hand-off kubeconfig — without you touching base64, jsonpath, or a temp file. Because Kubexer is local-first and bring-your-own-key, none of this round-trips through anyone's cloud; it talks to your cluster with your existing context and writes the result to your machine.&lt;/p&gt;

&lt;p&gt;What was fifteen careful steps becomes: choose what they can do, get back a scoped kubeconfig you can actually defend in a security review.&lt;/p&gt;

&lt;p&gt;If you've ever fat-fingered a CA cert at 6pm or handed out broader access than you meant to because the narrow path was too tedious, this is the itch I was scratching. I'd love feedback from anyone doing multi-team or regulated cluster access — that's the context that shaped it.&lt;/p&gt;

&lt;p&gt;Questions about the RBAC mechanics or the kubeconfig assembly are very welcome in the comments.&lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://www.kubexer.com" rel="noopener noreferrer"&gt;Kubexer Kubernetes IDE&lt;/a&gt; .. it’s a full fledged feature complete  Kubernetes IDE and its free with pro features.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>kubexer</category>
      <category>devops</category>
      <category>rbac</category>
    </item>
  </channel>
</rss>
