<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>RBAC Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/rbac/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/rbac/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Sat, 09 May 2026 18:40:47 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>RBAC Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/rbac/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>Kubernetes CRDs in Production: Finalizers, Status Conditions, and RBAC Patterns</title>
		<link>https://linuxcent.com/kubernetes-crd-production-finalizers-conditions-rbac/</link>
					<comments>https://linuxcent.com/kubernetes-crd-production-finalizers-conditions-rbac/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 25 Apr 2026 18:17:16 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[CRD]]></category>
		<category><![CDATA[Finalizers]]></category>
		<category><![CDATA[Owner References]]></category>
		<category><![CDATA[Production Kubernetes]]></category>
		<category><![CDATA[RBAC]]></category>
		<category><![CDATA[Status Conditions]]></category>
		<guid isPermaLink="false">https://linuxcent.com/kubernetes-crd-production-finalizers-conditions-rbac/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 8</span> <span class="rt-label rt-postfix">minutes</span></span>Ship production-ready Kubernetes CRDs: finalizer design patterns, status condition conventions, owner references, RBAC for custom resources, and common failure modes.</p>
<p>The post <a href="https://linuxcent.com/kubernetes-crd-production-finalizers-conditions-rbac/">Kubernetes CRDs in Production: Finalizers, Status Conditions, and RBAC Patterns</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 8</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>Kubernetes CRDs &amp; Operators: Extending the API, Episode 10</em><br />
<em><a href="/what-is-kubernetes-crd/">What Is a CRD?</a> · <a href="/kubernetes-custom-resources-examples/">CRDs You Already Use</a> · <a href="/kubernetes-crd-schema-explained/">CRD Anatomy</a> · <a href="/write-kubernetes-crd-yaml-walkthrough/">Write Your First CRD</a> · <a href="/kubernetes-crd-cel-validation/">CEL Validation</a> · <a href="/kubernetes-controller-reconcile-loop/">Controller Loop</a> · <a href="/build-kubernetes-operator-controller-runtime/">Build an Operator</a> · <a href="/kubernetes-crd-versioning-conversion-webhook/">CRD Versioning</a> · <a href="/kubernetes-admission-webhooks-explained/">Admission Webhooks</a> · </em><em><a href="/kubernetes-crd-production-finalizers-conditions-rbac/">CRDs in Production</a></em></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li><strong>Finalizers</strong> block deletion until cleanup completes — they prevent orphaned external resources but cause stuck objects if the controller crashes mid-cleanup; always implement a removal timeout</li>
<li><strong>Status conditions</strong> are the standard communication channel between controller and user: use <code class="" data-line="">type</code>, <code class="" data-line="">status</code>, <code class="" data-line="">reason</code>, <code class="" data-line="">message</code>, and <code class="" data-line="">observedGeneration</code> on every condition; never invent ad-hoc status fields</li>
<li><strong>Owner references</strong> wire automatic garbage collection — when the parent custom resource is deleted, Kubernetes deletes owned child objects; use them for every object your controller creates in the same namespace</li>
<li><strong>RBAC for CRDs in multi-tenant clusters</strong> must include separate ClusterRoles for controller, editor, and viewer; grant <code class="" data-line="">status</code> and <code class="" data-line="">finalizers</code> as separate sub-resources; never give application teams cluster-scoped create/delete on CRDs</li>
<li>The three most common Kubernetes CRD production failure modes: finalizer death loop, status thrash, and CRD deletion cascade — all avoidable with the patterns in this episode</li>
<li>Running <code class="" data-line="">kubectl get crds</code> on a healthy cluster should show <code class="" data-line="">Established: True</code> for every CRD; non-Established CRDs silently reject all create requests</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  PRODUCTION CRD LIFECYCLE: FULL PICTURE

  Create         Reconcile        Suspend/Resume      Delete
  ──────         ─────────        ──────────────      ──────
  User applies   Controller       User patches         User deletes
  BackupPolicy   creates CronJob, spec.suspended=true  BackupPolicy
      │          sets status          │                    │
      ▼              │                ▼                    ▼
  Admission      │           Controller          Finalizer blocks
  webhook        │           suspends CronJob     deletion
  (if any)       │                               Controller:
      │          │                                 1. Delete CronJob
      ▼          ▼                                 2. Remove external state
  Schema       Status                              3. Remove finalizer
  validation   conditions                          Object deleted from etcd
      │        updated
      ▼
  Controller
  reconcile
  triggered
</code></pre>
<p>Kubernetes CRD production readiness is not just about making the happy path work — it is about designing for the failure modes: controllers crashing mid-operation, deletion races, and status messages that confuse operators at 2am.</p>
<hr />
<h2 id="finalizers-controlled-deletion">Finalizers: Controlled Deletion</h2>
<p>A finalizer is a string in <code class="" data-line="">metadata.finalizers</code>. Kubernetes will not delete an object that has finalizers, regardless of who issues the delete command.</p>
<pre><code class="" data-line="">metadata:
  name: nightly
  namespace: demo
  finalizers:
    - storage.example.com/backup-cleanup  # ← your controller put this here
</code></pre>
<p>When <code class="" data-line="">kubectl delete bp nightly</code> runs:</p>
<pre><code class="" data-line="">  1. API server sets metadata.deletionTimestamp  (does NOT delete yet)
  2. Object is visible as &quot;Terminating&quot;
  3. Controller sees deletionTimestamp set
  4. Controller runs cleanup:
       - delete backup data from S3
       - delete CronJob (or let owner references handle it)
       - release any external locks
  5. Controller removes the finalizer:
       patch bp nightly --type=json \
         -p &#039;[{&quot;op&quot;:&quot;remove&quot;,&quot;path&quot;:&quot;/metadata/finalizers/0&quot;}]&#039;
  6. API server sees finalizers list is now empty → deletes the object
</code></pre>
<h3 id="adding-a-finalizer-in-go">Adding a finalizer in Go</h3>
<pre><code class="" data-line="">const finalizerName = &quot;storage.example.com/backup-cleanup&quot;

func (r *BackupPolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    bp := &amp;storagev1alpha1.BackupPolicy{}
    if err := r.Get(ctx, req.NamespacedName, bp); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // Deletion path
    if !bp.DeletionTimestamp.IsZero() {
        if controllerutil.ContainsFinalizer(bp, finalizerName) {
            if err := r.cleanupExternalResources(ctx, bp); err != nil {
                return ctrl.Result{}, err
            }
            controllerutil.RemoveFinalizer(bp, finalizerName)
            if err := r.Update(ctx, bp); err != nil {
                return ctrl.Result{}, err
            }
        }
        return ctrl.Result{}, nil
    }

    // Normal path: ensure finalizer is present
    if !controllerutil.ContainsFinalizer(bp, finalizerName) {
        controllerutil.AddFinalizer(bp, finalizerName)
        if err := r.Update(ctx, bp); err != nil {
            return ctrl.Result{}, err
        }
    }

    // ... rest of reconcile
}
</code></pre>
<h3 id="finalizer-death-loop-and-the-timeout-pattern">Finalizer death loop and the timeout pattern</h3>
<p>If <code class="" data-line="">cleanupExternalResources</code> always returns an error (external system down, bug in cleanup code), the object gets stuck in <code class="" data-line="">Terminating</code> forever. The operator cannot delete it; <code class="" data-line="">kubectl delete --force</code> does not help with finalizers.</p>
<p>Prevention: add a cleanup deadline with status tracking.</p>
<pre><code class="" data-line="">func (r *BackupPolicyReconciler) cleanupExternalResources(ctx context.Context, bp *storagev1alpha1.BackupPolicy) error {
    // Check if we&#039;ve been trying to clean up for too long
    if bp.DeletionTimestamp != nil {
        deadline := bp.DeletionTimestamp.Add(10 * time.Minute)
        if time.Now().After(deadline) {
            // Log the failure, abandon cleanup, let the object be deleted.
            log.FromContext(ctx).Error(nil, &quot;cleanup deadline exceeded, removing finalizer anyway&quot;,
                &quot;name&quot;, bp.Name)
            return nil   // returning nil removes the finalizer
        }
    }
    // ... actual cleanup
}
</code></pre>
<p>Recovery for a stuck object (use only when cleanup truly cannot succeed):</p>
<pre><code class="" data-line="">kubectl patch bp nightly -n demo --type=json \
  -p &#039;[{&quot;op&quot;:&quot;remove&quot;,&quot;path&quot;:&quot;/metadata/finalizers&quot;}]&#039;
</code></pre>
<hr />
<h2 id="status-conditions-the-right-way">Status Conditions: The Right Way</h2>
<p>The Kubernetes standard condition format is defined in <code class="" data-line="">k8s.io/apimachinery/pkg/apis/meta/v1.Condition</code>:</p>
<pre><code class="" data-line="">type Condition struct {
    Type               string          // e.g. &quot;Ready&quot;, &quot;Synced&quot;, &quot;Degraded&quot;
    Status             ConditionStatus // &quot;True&quot;, &quot;False&quot;, &quot;Unknown&quot;
    ObservedGeneration int64           // the .metadata.generation this condition reflects
    LastTransitionTime metav1.Time     // when Status last changed
    Reason             string          // machine-readable, CamelCase, e.g. &quot;CronJobCreated&quot;
    Message            string          // human-readable, may contain details
}
</code></pre>
<h3 id="standard-condition-types">Standard condition types</h3>
<table>
<thead>
<tr>
<th>Type</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">Ready</code></td>
<td>The resource is fully reconciled and operational</td>
</tr>
<tr>
<td><code class="" data-line="">Synced</code></td>
<td>The resource has been synced with an external system</td>
</tr>
<tr>
<td><code class="" data-line="">Progressing</code></td>
<td>An operation is actively in progress</td>
</tr>
<tr>
<td><code class="" data-line="">Degraded</code></td>
<td>The resource is operating in a reduced capacity</td>
</tr>
</tbody>
</table>
<p>Use <code class="" data-line="">Ready: True</code> only when the full reconcile is complete and the resource is functional. Use <code class="" data-line="">Ready: False</code> with a clear <code class="" data-line="">Message</code> when reconcile fails or is blocked.</p>
<h3 id="setting-conditions-in-go">Setting conditions in Go</h3>
<pre><code class="" data-line="">meta.SetStatusCondition(&amp;bpCopy.Status.Conditions, metav1.Condition{
    Type:               &quot;Ready&quot;,
    Status:             metav1.ConditionFalse,
    ObservedGeneration: bp.Generation,
    Reason:             &quot;CronJobCreateFailed&quot;,
    Message:            fmt.Sprintf(&quot;failed to create CronJob: %v&quot;, err),
})
</code></pre>
<p><code class="" data-line="">meta.SetStatusCondition</code> handles deduplication — it updates an existing condition of the same <code class="" data-line="">Type</code> rather than appending a duplicate.</p>
<h3 id="observedgeneration-is-critical"><code class="" data-line="">observedGeneration</code> is critical</h3>
<pre><code class="" data-line="">metadata.generation      = 5   (increments on every spec change)
status.observedGeneration = 3  (set by controller on each reconcile)

If observedGeneration &lt; generation:
  → controller has not yet reconciled the latest spec change
  → status.conditions reflect an older state
  → do NOT alert based on conditions that lag generation
</code></pre>
<p>Always set <code class="" data-line="">ObservedGeneration: bp.Generation</code> when writing status conditions. Tooling (Argo CD, Flux, kubectl wait) depends on this to know whether status is current.</p>
<h3 id="kubectl-wait-uses-conditions"><code class="" data-line="">kubectl wait</code> uses conditions</h3>
<pre><code class="" data-line=""># Wait until BackupPolicy is Ready
kubectl wait bp/nightly -n demo \
  --for=condition=Ready \
  --timeout=60s
</code></pre>
<p>This works because <code class="" data-line="">kubectl wait</code> reads the <code class="" data-line="">status.conditions</code> array.</p>
<hr />
<h2 id="owner-references-automatic-garbage-collection">Owner References: Automatic Garbage Collection</h2>
<p>Owner references wire a parent-child relationship between Kubernetes objects. When the parent is deleted, Kubernetes garbage-collects all owned children automatically.</p>
<pre><code class="" data-line="">metadata:
  name: nightly-backup       # CronJob
  ownerReferences:
    - apiVersion: storage.example.com/v1alpha1
      kind: BackupPolicy
      name: nightly
      uid: a1b2c3d4-...
      controller: true          # only one owner can be the controller
      blockOwnerDeletion: true  # the GC waits for this owner before deleting child
</code></pre>
<p>Set in Go using <code class="" data-line="">ctrl.SetControllerReference</code>:</p>
<pre><code class="" data-line="">if err := ctrl.SetControllerReference(bp, cronJob, r.Scheme); err != nil {
    return ctrl.Result{}, err
}
</code></pre>
<h3 id="owner-reference-rules">Owner reference rules</h3>
<ul>
<li>Owner and owned object must be in the <strong>same namespace</strong> — cluster-scoped objects cannot own namespaced objects</li>
<li>Only one object can be the <code class="" data-line="">controller: true</code> owner; others can be non-controller owners</li>
<li>Deleting the owner cascades to deleting owned objects — this is garbage collection, not finalizer-based cleanup</li>
</ul>
<p>Without owner references, deleting a BackupPolicy leaves the CronJob as an orphan. This is hard to detect and accumulates over time.</p>
<hr />
<h2 id="rbac-patterns-for-multi-tenant-crd-usage">RBAC Patterns for Multi-Tenant CRD Usage</h2>
<p>A production CRD deployment needs three distinct RBAC roles:</p>
<pre><code class="" data-line=""># 1. Controller role — full access for the operator
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: backuppolicy-controller
rules:
  - apiGroups: [&quot;storage.example.com&quot;]
    resources: [&quot;backuppolicies&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;, &quot;update&quot;, &quot;patch&quot;]
  - apiGroups: [&quot;storage.example.com&quot;]
    resources: [&quot;backuppolicies/status&quot;]
    verbs: [&quot;get&quot;, &quot;update&quot;, &quot;patch&quot;]
  - apiGroups: [&quot;storage.example.com&quot;]
    resources: [&quot;backuppolicies/finalizers&quot;]
    verbs: [&quot;update&quot;]
  - apiGroups: [&quot;batch&quot;]
    resources: [&quot;cronjobs&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;, &quot;create&quot;, &quot;update&quot;, &quot;patch&quot;, &quot;delete&quot;]
---
# 2. Editor role — for application teams (namespaced binding)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: backuppolicy-editor
rules:
  - apiGroups: [&quot;storage.example.com&quot;]
    resources: [&quot;backuppolicies&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;, &quot;create&quot;, &quot;update&quot;, &quot;patch&quot;, &quot;delete&quot;]
  # No status write — only the controller writes status
  # No finalizers write — prevents deletion blocking by non-controllers
---
# 3. Viewer role — for audit, monitoring
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: backuppolicy-viewer
rules:
  - apiGroups: [&quot;storage.example.com&quot;]
    resources: [&quot;backuppolicies&quot;]
    verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
</code></pre>
<p>Bind editor/viewer roles at <strong>namespace</strong> scope, not cluster scope:</p>
<pre><code class="" data-line="">apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: team-alpha-backup-editor
  namespace: team-alpha
subjects:
  - kind: Group
    name: team-alpha
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: backuppolicy-editor
  apiGroup: rbac.authorization.k8s.io
</code></pre>
<p>This pattern gives team-alpha full control over BackupPolicies in their namespace but no access to other namespaces — standard Kubernetes multi-tenancy.</p>
<hr />
<h2 id="the-three-production-failure-modes">The Three Production Failure Modes</h2>
<h3 id="1-finalizer-death-loop">1. Finalizer death loop</h3>
<p><strong>Symptoms:</strong> Object stuck in <code class="" data-line="">Terminating</code> for hours; <code class="" data-line="">kubectl get bp nightly</code> shows <code class="" data-line="">DeletionTimestamp</code> set but object exists.</p>
<p><strong>Cause:</strong> <code class="" data-line="">cleanupExternalResources</code> always returns an error.</p>
<p><strong>Detection:</strong></p>
<pre><code class="" data-line="">kubectl get bp nightly -n demo -o jsonpath=&#039;{.metadata.deletionTimestamp}&#039;
# non-empty = stuck in termination
kubectl describe bp nightly -n demo
# look for repeated reconcile error events
</code></pre>
<p><strong>Fix:</strong> Add cleanup deadline in controller; use <code class="" data-line="">kubectl patch</code> to remove finalizer as last resort.</p>
<h3 id="2-status-thrash">2. Status thrash</h3>
<p><strong>Symptoms:</strong> Controller sets <code class="" data-line="">Ready: True</code>, then <code class="" data-line="">Ready: False</code>, then <code class="" data-line="">Ready: True</code> in a rapid loop. Alert noise, confusing dashboards.</p>
<p><strong>Cause:</strong> Each reconcile compares actual state incorrectly due to cache lag — it sees its own status write as a change, re-reconciles, and flips the status again.</p>
<p><strong>Fix:</strong> Set <code class="" data-line="">ObservedGeneration</code> on every condition. Compare <code class="" data-line="">generation</code> with <code class="" data-line="">observedGeneration</code> before re-reconciling. Use <code class="" data-line="">meta.IsStatusConditionTrue</code> to check current condition before overwriting it with the same value.</p>
<pre><code class="" data-line="">// Only update status if it changed
current := meta.FindStatusCondition(bp.Status.Conditions, &quot;Ready&quot;)
if current == nil || current.Status != desired.Status || current.Reason != desired.Reason {
    meta.SetStatusCondition(&amp;bpCopy.Status.Conditions, desired)
    r.Status().Update(ctx, bpCopy)
}
</code></pre>
<h3 id="3-crd-deletion-cascade">3. CRD deletion cascade</h3>
<p><strong>Symptoms:</strong> A team deletes a CRD for cleanup purposes; all instances across all namespaces disappear silently.</p>
<p><strong>Cause:</strong> <code class="" data-line="">kubectl delete crd backuppolicies.storage.example.com</code> — the API server cascades the deletion to all custom resources of that type.</p>
<p><strong>Prevention:</strong><br />
&#8211; Add a <code class="" data-line="">resourcelock</code> annotation on production CRDs managed by your operator<br />
&#8211; Use GitOps (Argo CD, Flux) to manage CRD installation — a deleted CRD is automatically re-applied from the Git source<br />
&#8211; Back up CRDs and instances with <code class="" data-line="">velero</code> or equivalent before any CRD management operations</p>
<hr />
<h2 id="production-readiness-checklist">Production Readiness Checklist</h2>
<pre><code class="" data-line="">CRD DEFINITION
  □ spec.versions has exactly one storage: true version
  □ Status subresource enabled (subresources.status: {})
  □ additionalPrinterColumns includes Ready column from status.conditions
  □ OpenAPI schema defines required fields and types
  □ CEL rules cover cross-field constraints

CONTROLLER
  □ Owner references set on all child resources
  □ Finalizer logic includes cleanup deadline
  □ Status conditions use standard format with observedGeneration
  □ Reconcile function is idempotent
  □ Not-found errors handled cleanly (return nil, not error)
  □ At least 2 replicas with leader election enabled

RBAC
  □ Three ClusterRoles: controller, editor, viewer
  □ Status and finalizers are separate RBAC sub-resources
  □ Editor/viewer bound at namespace scope, not cluster scope
  □ Controller ServiceAccount has only necessary permissions

OPERATIONS
  □ CRD installed via GitOps or Helm (not manual kubectl apply)
  □ Backup of CRDs and instances included in cluster backup
  □ kubectl get crds shows Established: True for all CRDs
  □ Monitoring for stuck Terminating objects (finalizer deadlock)
  □ Alert on controller reconcile error rate, not just pod health
</code></pre>
<hr />
<h2 id="common-mistakes"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Mistakes</h2>
<p><strong>Granting <code class="" data-line="">update</code> on <code class="" data-line="">backuppolicies</code> but not <code class="" data-line="">backuppolicies/status</code> to the controller.</strong> If the controller cannot write status, status updates silently fail. The controller appears to run but status conditions never update. Grant both <code class="" data-line="">backuppolicies</code> (for spec/metadata writes) and <code class="" data-line="">backuppolicies/status</code> (for the status subresource path).</p>
<p><strong>Setting <code class="" data-line="">Ready: True</code> before all owned resources are healthy.</strong> If the controller sets <code class="" data-line="">Ready: True</code> after creating the CronJob but before verifying the CronJob is actually <code class="" data-line="">active</code>, users see a false-positive health signal. Only set <code class="" data-line="">Ready: True</code> when you have confirmed the desired state is actually achieved.</p>
<p><strong>Not setting <code class="" data-line="">observedGeneration</code> on status conditions.</strong> Tools like Argo CD and <code class="" data-line="">kubectl wait --for=condition=Ready</code> will report incorrect health status if <code class="" data-line="">observedGeneration</code> is stale. Always set <code class="" data-line="">ObservedGeneration: obj.Generation</code> in every condition write.</p>
<p><strong>Using <code class="" data-line="">kubectl delete crd</code> in a production cluster without a backup.</strong> This is irreversible. Treat CRDs as production-critical infrastructure — require GitOps review, backup verification, and team approval before any CRD deletion.</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line=""># Check for stuck Terminating objects
kubectl get backuppolicies -A --field-selector metadata.deletionTimestamp!=&#039;&#039;

# Force-remove a stuck finalizer (use only when cleanup is truly impossible)
kubectl patch bp nightly -n demo --type=json \
  -p &#039;[{&quot;op&quot;:&quot;remove&quot;,&quot;path&quot;:&quot;/metadata/finalizers/0&quot;}]&#039;

# Check all CRDs are Established
kubectl get crds -o jsonpath=&#039;{range .items[*]}{.metadata.name} {.status.conditions[?(@.type==&quot;Established&quot;)].status}{&quot;\n&quot;}{end}&#039;

# Watch status conditions update during reconcile
kubectl get bp nightly -n demo -w -o \
  jsonpath=&#039;{.status.conditions[?(@.type==&quot;Ready&quot;)].status} {.status.conditions[?(@.type==&quot;Ready&quot;)].message}{&quot;\n&quot;}&#039;

# Verify owner references are set on child CronJob
kubectl get cronjob nightly-backup -n demo \
  -o jsonpath=&#039;{.metadata.ownerReferences}&#039;

# List all objects owned by a BackupPolicy (by label)
kubectl get all -n demo -l backuppolicy=nightly
</code></pre>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Finalizers block deletion until cleanup completes — always implement a cleanup deadline to prevent permanent stuck objects</li>
<li>Status conditions must use the standard format with <code class="" data-line="">observedGeneration</code> — tooling depends on it for correctness</li>
<li>Owner references enable automatic garbage collection of child resources when the parent is deleted</li>
<li>RBAC needs three roles (controller, editor, viewer) with <code class="" data-line="">status</code> and <code class="" data-line="">finalizers</code> as separate sub-resources</li>
<li>The three production failure modes — finalizer death loop, status thrash, CRD deletion cascade — are all preventable with the patterns covered in this episode</li>
</ul>
<hr />
<h2 id="series-complete">Series Complete</h2>
<p>You now have the full picture of Kubernetes CRDs and Operators: from understanding what a CRD is (<a href="/what-is-kubernetes-crd/">EP01</a>), through real examples (<a href="/kubernetes-custom-resources-examples/">EP02</a>), schema design (<a href="/kubernetes-crd-schema-explained/">EP03</a>), hands-on YAML (<a href="/write-kubernetes-crd-yaml-walkthrough/">EP04</a>), CEL validation (<a href="/kubernetes-crd-cel-validation/">EP05</a>), the controller loop (<a href="/kubernetes-controller-reconcile-loop/">EP06</a>), building an operator (<a href="/build-kubernetes-operator-controller-runtime/">EP07</a>), versioning (<a href="/kubernetes-crd-versioning-conversion-webhook/">EP08</a>), admission webhooks (<a href="/kubernetes-admission-webhooks-explained/">EP09</a>), to production patterns in this episode.</p>
<p>The next series in the Kubernetes learning arc on linuxcent.com covers <strong>Kubernetes Networking Deep Dive</strong> — Services, Ingress, Gateway API, CNI, and eBPF networking. Subscribe below to get it when it launches.</p>
<p>Stay subscribed → <a href="https://linuxcent.com">linuxcent.com</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-production-finalizers-conditions-rbac%2F&amp;linkname=Kubernetes%20CRDs%20in%20Production%3A%20Finalizers%2C%20Status%20Conditions%2C%20and%20RBAC%20Patterns" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-production-finalizers-conditions-rbac%2F&amp;linkname=Kubernetes%20CRDs%20in%20Production%3A%20Finalizers%2C%20Status%20Conditions%2C%20and%20RBAC%20Patterns" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-production-finalizers-conditions-rbac%2F&amp;linkname=Kubernetes%20CRDs%20in%20Production%3A%20Finalizers%2C%20Status%20Conditions%2C%20and%20RBAC%20Patterns" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-production-finalizers-conditions-rbac%2F&amp;linkname=Kubernetes%20CRDs%20in%20Production%3A%20Finalizers%2C%20Status%20Conditions%2C%20and%20RBAC%20Patterns" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-production-finalizers-conditions-rbac%2F&amp;linkname=Kubernetes%20CRDs%20in%20Production%3A%20Finalizers%2C%20Status%20Conditions%2C%20and%20RBAC%20Patterns" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-production-finalizers-conditions-rbac%2F&amp;linkname=Kubernetes%20CRDs%20in%20Production%3A%20Finalizers%2C%20Status%20Conditions%2C%20and%20RBAC%20Patterns" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-production-finalizers-conditions-rbac%2F&amp;linkname=Kubernetes%20CRDs%20in%20Production%3A%20Finalizers%2C%20Status%20Conditions%2C%20and%20RBAC%20Patterns" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-production-finalizers-conditions-rbac%2F&#038;title=Kubernetes%20CRDs%20in%20Production%3A%20Finalizers%2C%20Status%20Conditions%2C%20and%20RBAC%20Patterns" data-a2a-url="https://linuxcent.com/kubernetes-crd-production-finalizers-conditions-rbac/" data-a2a-title="Kubernetes CRDs in Production: Finalizers, Status Conditions, and RBAC Patterns"></a></p><p>The post <a href="https://linuxcent.com/kubernetes-crd-production-finalizers-conditions-rbac/">Kubernetes CRDs in Production: Finalizers, Status Conditions, and RBAC Patterns</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/kubernetes-crd-production-finalizers-conditions-rbac/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1702</post-id>	</item>
		<item>
		<title>IAM Roles vs Policies: How Cloud Authorization Actually Works</title>
		<link>https://linuxcent.com/iam-roles-policies-permissions-explained/</link>
					<comments>https://linuxcent.com/iam-roles-policies-permissions-explained/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Tue, 14 Apr 2026 05:05:54 +0000</pubDate>
				<category><![CDATA[Cloud IAM]]></category>
		<category><![CDATA[ABAC]]></category>
		<category><![CDATA[AWS IAM Policy]]></category>
		<category><![CDATA[Cloud Security]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[IAM Policies]]></category>
		<category><![CDATA[IAM Roles]]></category>
		<category><![CDATA[Least Privilege]]></category>
		<category><![CDATA[RBAC]]></category>
		<guid isPermaLink="false">https://linuxcent.com/iam-roles-policies-permissions-explained/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 12</span> <span class="rt-label rt-postfix">minutes</span></span>A clear breakdown of IAM roles, policies, and permissions — the building blocks of cloud access control in AWS, GCP, and Azure with RBAC and ABAC patterns.</p>
<p>The post <a href="https://linuxcent.com/iam-roles-policies-permissions-explained/">IAM Roles vs Policies: How Cloud Authorization Actually Works</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 12</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<hr />
<p><a href="/what-is-cloud-iam/">What Is Cloud IAM</a> → <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization</a> → <strong>IAM Roles vs Policies</strong> → <a href="/aws-iam-deep-dive/">AWS IAM Deep Dive</a> → <a href="/gcp-iam-deep-dive/">GCP Resource Hierarchy IAM</a> → <a href="/azure-rbac-entra-id-guide/">Azure RBAC Scopes</a></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Every cloud permission is atomic: one action (<code class="" data-line="">s3:GetObject</code>) on one resource class — the indivisible unit of access</li>
<li>Policies group permissions into documents with conditions; roles carry policies and are assigned to identities</li>
<li>Never attach policies directly to users — roles are the indirection layer that makes access auditable and revocable</li>
<li>AWS roles have <strong>two</strong> required configs: trust policy (who can assume) + permission policy (what they can do) — both must be right</li>
<li>GCP binds roles to resources; AWS attaches policies to identities — the mental models run in <strong>opposite directions</strong></li>
<li><code class="" data-line="">iam:PassRole</code> in AWS and <code class="" data-line="">iam.serviceAccounts.actAs</code> in GCP are privilege escalation vectors — always scope to specific ARNs, never <code class="" data-line="">*</code></li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<p>Three primitives underlie every cloud IAM system. Learn how they connect and any cloud access model becomes readable.</p>
<pre><code class="" data-line="">  THE THREE-LAYER STACK
  Build bottom-up. Assign top-down. Change one layer without touching the others.

  ┌──────────────────────────────────────────────────────────────────────┐
  │  LAYER 3 — IDENTITY                                                  │
  │  alice@company.com  ·  backend-service  ·  ci-runner@proj           │
  │  &quot;who is acting — a human, a service, or a machine&quot;                 │
  ├──────────────────────────────────────────────────────────────────────┤
  │  LAYER 2 — ROLE                                                      │
  │  BackendDeveloper  ·  DataAnalyst  ·  DeployBot  ·  S3ReadOnly      │
  │  &quot;what function does this identity serve — the job title&quot;           │
  ├──────────────────────────────────────────────────────────────────────┤
  │  LAYER 1 — POLICY                                                    │
  │  AllowS3Read  ·  AllowECRPush  ·  DenyProdDelete  ·  RequireMFA    │
  │  &quot;what is explicitly permitted or denied, under what conditions&quot;    │
  ├──────────────────────────────────────────────────────────────────────┤
  │  LAYER 0 — PERMISSION                                                │
  │  s3:GetObject  ·  ecr:PutImage  ·  s3:DeleteObject  ·  iam:PassRole│
  │  &quot;one verb on one class of resource — the atom of access control&quot;  │
  └──────────────────────────────────────────────────────────────────────┘

  When alice joins the backend team → assign her the BackendDeveloper role
  When the S3 bucket changes → update the policy once; alice gets it automatically
  When alice leaves → remove the role assignment; policy and permissions are untouched
</code></pre>
<p>If this maps better to something physical:</p>
<pre><code class="" data-line="">  PHYSICAL WORLD            →    CLOUD IAM

  A specific door rule           Permission      s3:GetObject
  Keycard access profile    →    Policy          AllowS3Read
  Job title                 →    Role            BackendDeveloper
  The employee              →    Identity        alice@company.com

  When the employee leaves: revoke the role assignment.
  The job title, the keycard profile, the door rules — all unchanged.
  Next hire gets the same role. Same access. No manual work.
</code></pre>
<hr />
<h2 id="introduction">Introduction</h2>
<p>IAM roles vs policies is a distinction that defines how cloud authorization actually works — and getting it wrong is how access sprawl starts. Every <a href="/authentication-vs-authorization-iam/">authentication vs authorization</a> failure at the authorization layer traces back to how these three primitives are — or aren&#8217;t — structured.</p>
<p>Every cloud IAM system — AWS, GCP, Azure — is built on the same three primitives: permissions, policies, and roles. Learn these well and any cloud provider becomes readable. Skip them and you spend years pattern-matching without understanding why anything is structured the way it is.</p>
<p><a href="/what-is-cloud-iam/">What Is Cloud IAM</a> established the foundation: IAM is the system that governs who can access what in cloud infrastructure, and its default answer is always deny. <a href="/authentication-vs-authorization-iam/">Authentication vs Authorization: AWS AccessDenied Explained</a> drew the line between authentication — proving identity — and authorization — proving you&#8217;re allowed to act. This episode is about the authorization layer specifically. These three building blocks are how authorization is expressed in practice.</p>
<p>Before walking through each one, here&#8217;s what access control looks like without any of this structure — because that&#8217;s the fastest way to understand why the layers exist.</p>
<p>In 2015 I inherited an AWS account from a 12-engineer team that had been building for 18 months. When I ran <code class="" data-line="">aws iam list-attached-user-policies</code> across the 23 users, 17 had policies attached directly to the user object — not to groups, not to roles.</p>
<p>One engineer had left six months earlier. His access key was still active. Three policies still attached: read access to prod S3, write to a DynamoDB table, ability to invoke Lambda functions. When I asked what the DynamoDB table was for, nobody could tell me. The Lambda functions no longer existed.</p>
<p>That account wasn&#8217;t built by negligent engineers. It was built by engineers reaching for whatever granted access fastest, under deadline, without a framework. Permissions scattered. Nothing tracked. Nothing removed.</p>
<p>Roles, policies, and permissions are the framework that prevents that. Understanding them is the difference between an IAM configuration you can audit in an afternoon and one that takes a week and still leaves you uncertain.</p>
<hr />
<h2 id="what-are-iam-permissions-the-atomic-unit-of-access-control">What Are IAM Permissions? The Atomic Unit of Access Control</h2>
<p>A <strong>permission</strong> is a single action on a class of resources. It is the most granular thing you can grant or deny — the atom of access control.</p>
<p>Cloud providers express permissions differently, but the structure is consistent: a service, a resource type, and an action verb.</p>
<pre><code class="" data-line=""># AWS: service:Action
s3:GetObject               # read an object from S3
ec2:StartInstances         # start EC2 instances
iam:PassRole               # assign a role to an AWS service — one of the most dangerous
kms:Decrypt                # use a KMS key to decrypt

# GCP: service.resource.verb
storage.objects.get
compute.instances.start
iam.serviceAccounts.actAs  # impersonate a service account — equivalent risk to iam:PassRole
cloudkms.cryptoKeyVersions.useToDecrypt

# Azure: Provider/ResourceType/Action
Microsoft.Storage/storageAccounts/blobServices/containers/read
Microsoft.Compute/virtualMachines/start/action
Microsoft.Authorization/roleAssignments/write   # grant roles — highest risk
Microsoft.KeyVault/vaults/secrets/getSecret/action
</code></pre>
<p>You generally don&#8217;t assign individual permissions directly to identities — that&#8217;s like handing someone 47 keys with no labels and expecting the system to remain auditable. Permissions are grouped into policies.</p>
<hr />
<h2 id="what-are-iam-policies-grouping-permissions-with-conditions">What Are IAM Policies? Grouping Permissions with Conditions</h2>
<p>A <strong>policy</strong> is a document that groups permissions and defines the conditions under which they apply.</p>
<h3 id="aws-policy-structure">AWS policy structure</h3>
<p>An AWS policy document is JSON. Every field is a deliberate decision:</p>
<pre><code class="" data-line="">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [
    {
      &quot;Sid&quot;: &quot;AllowReadS3Backups&quot;,
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Action&quot;: [&quot;s3:GetObject&quot;, &quot;s3:ListBucket&quot;],
      &quot;Resource&quot;: [
        &quot;arn:aws:s3:::company-backups&quot;,
        &quot;arn:aws:s3:::company-backups/*&quot;
      ],
      &quot;Condition&quot;: {
        &quot;StringEquals&quot;: { &quot;s3:prefix&quot;: [&quot;2024/&quot;, &quot;2025/&quot;] }
      }
    },
    {
      &quot;Sid&quot;: &quot;DenyDeleteEverywhere&quot;,
      &quot;Effect&quot;: &quot;Deny&quot;,
      &quot;Action&quot;: &quot;s3:DeleteObject&quot;,
      &quot;Resource&quot;: &quot;*&quot;
    }
  ]
}
</code></pre>
<p>The <code class="" data-line="">Sid</code> is a comment — use it. <code class="" data-line="">AllowReadS3Backups</code> tells a future auditor why this statement exists. <code class="" data-line="">Statement1</code> is technical debt.</p>
<p>The <code class="" data-line="">Effect</code> is either <code class="" data-line="">Allow</code> or <code class="" data-line="">Deny</code>. A <code class="" data-line="">Deny</code> always wins — it cannot be overridden by any <code class="" data-line="">Allow</code> anywhere in any policy on the same identity. If you have a <code class="" data-line="">Deny</code> on <code class="" data-line="">s3:DeleteObject</code> with <code class="" data-line="">&quot;Resource&quot;: &quot;*&quot;</code>, nothing can grant delete access to that identity. This asymmetry is deliberate: it&#8217;s how guardrails work.</p>
<p>The <code class="" data-line="">Resource</code> field is where access most often creeps wider than intended. <code class="" data-line="">&quot;Resource&quot;: &quot;*&quot;</code> on a write action means &#8220;every resource of this type in the account.&#8221; It works. It outlives the context that made it feel reasonable.</p>
<h3 id="aws-policy-types-which-to-reach-for">AWS policy types — which to reach for</h3>
<pre><code class="" data-line="">┌──────────────────────────┬────────────────────────────┬────────────────────────────┐
│ Type                     │ Attached to                │ What it does               │
├──────────────────────────┼────────────────────────────┼────────────────────────────┤
│ Identity-based           │ User, Group, Role          │ What the identity can do   │
│ Resource-based           │ S3 bucket, KMS key, Lambda │ Who can touch this resource │
│ Permissions boundary     │ User or Role               │ Maximum possible — ceiling  │
│ Service Control Policy   │ AWS Org OU or Account      │ Org-level guardrail         │
│ Session policy           │ AssumeRole session         │ Restricts a specific session│
│ Resource Control Policy  │ AWS Org resources          │ Resource-level org guardrail│
└──────────────────────────┴────────────────────────────┴────────────────────────────┘
</code></pre>
<p>Critical: <strong>Permissions boundaries and SCPs do not grant permissions</strong>. They constrain them. A boundary that allows <code class="" data-line="">s3:*</code> doesn&#8217;t mean the identity has S3 access. It means the identity <em>can have at most</em> S3 access, if an identity-based policy actually grants it. Many engineers set a boundary and expect it to work as a grant. It doesn&#8217;t.</p>
<h3 id="gcp-policy-bindings">GCP policy bindings</h3>
<p>GCP doesn&#8217;t attach policy documents to identities. Each resource has an IAM policy — a set of <strong>bindings</strong> mapping roles to members:</p>
<pre><code class="" data-line="">{
  &quot;bindings&quot;: [
    {
      &quot;role&quot;: &quot;roles/storage.objectViewer&quot;,
      &quot;members&quot;: [
        &quot;user:alice@company.com&quot;,
        &quot;serviceAccount:app-backend@project.iam.gserviceaccount.com&quot;
      ]
    },
    {
      &quot;role&quot;: &quot;roles/storage.objectCreator&quot;,
      &quot;members&quot;: [&quot;serviceAccount:upload-service@project.iam.gserviceaccount.com&quot;],
      &quot;condition&quot;: {
        &quot;title&quot;: &quot;Business hours only&quot;,
        &quot;expression&quot;: &quot;request.time.getHours(&#039;America/New_York&#039;) &gt;= 9 &amp;&amp; request.time.getHours(&#039;America/New_York&#039;) &lt; 18&quot;
      }
    }
  ]
}
</code></pre>
<p>The mental model shift: in AWS you ask &#8220;what can this identity do?&#8221; by looking at the identity. In GCP you ask &#8220;who can access this resource?&#8221; by looking at the resource. The question runs in the opposite direction.</p>
<h3 id="azure-role-definitions">Azure role definitions</h3>
<p>Azure separates what a role grants (role definition) from who gets it where (role assignment). Define once, assign at multiple scopes.</p>
<pre><code class="" data-line="">{
  &quot;Name&quot;: &quot;Custom Storage Reader&quot;,
  &quot;IsCustom&quot;: true,
  &quot;Actions&quot;: [
    &quot;Microsoft.Storage/storageAccounts/blobServices/containers/read&quot;,
    &quot;Microsoft.Storage/storageAccounts/blobServices/generateUserDelegationKey/action&quot;
  ],
  &quot;DataActions&quot;: [
    &quot;Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read&quot;
  ],
  &quot;AssignableScopes&quot;: [&quot;/subscriptions/SUB_ID&quot;]
}
</code></pre>
<p><code class="" data-line="">Actions</code> vs <code class="" data-line="">DataActions</code> catches people. <code class="" data-line="">Actions</code> are control plane — you can see the storage account exists. <code class="" data-line="">DataActions</code> are data plane — you can read actual blob contents. A user with <code class="" data-line="">Actions</code> can list the container but cannot read a single byte without a <code class="" data-line="">DataAction</code>. Both planes must be covered for the access to be complete.</p>
<hr />
<h2 id="what-are-iam-roles-the-layer-that-scales-access-control">What Are IAM Roles? The Layer That Scales Access Control</h2>
<p>A <strong>role</strong> is a collection of policies assigned to identities. It&#8217;s the indirection layer that makes access manageable at scale.</p>
<p>Going back to the 2015 account: the problem wasn&#8217;t that engineers had access — they needed it. The problem was that access was scattered across 23 individual user objects with no shared structure. This is what <a href="/what-is-cloud-iam/">what is cloud IAM</a> establishes as the core problem IAM exists to solve. Roles are the structural answer.</p>
<p>The role model solves this:</p>
<pre><code class="" data-line="">Policy: S3ReadAccess (s3:GetObject, s3:ListBucket on s3:::app-data/*)
  ↓ attached to
Role: BackendDeveloper
  ↓ assigned to
Users: alice, bob, charlie, dave (and six more)

When the bucket changes  → update one policy
When someone joins       → assign one role
When someone leaves      → remove one role
Access model stays coherent because it&#039;s structured.
</code></pre>
<h3 id="aws-roles-the-identity-that-issues-temporary-credentials">AWS roles — the identity that issues temporary credentials</h3>
<p>AWS roles are themselves IAM identities, not just permission containers. When something assumes a role, it gets temporary credentials from STS. Two things must be configured:</p>
<p><strong>Trust policy — who can assume:</strong></p>
<pre><code class="" data-line="">{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: [{
    &quot;Effect&quot;: &quot;Allow&quot;,
    &quot;Principal&quot;: { &quot;Service&quot;: &quot;ec2.amazonaws.com&quot; },
    &quot;Action&quot;: &quot;sts:AssumeRole&quot;
  }]
}
</code></pre>
<p>Without this, nobody can use the role regardless of its permissions. The trust policy is the gatekeeper.</p>
<p><strong>Permission policy — what it can do:</strong></p>
<pre><code class="" data-line="">aws iam create-role \
  --role-name AppServerRole \
  --assume-role-policy-document file://ec2-trust-policy.json

aws iam attach-role-policy \
  --role-name AppServerRole \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
</code></pre>
<p>When debugging &#8220;why can&#8217;t this Lambda/EC2/ECS task do X?&#8221;, the first thing I check is the trust policy. Many times the permission policy is correct — the service simply isn&#8217;t in the trust policy and cannot assume the role at all.</p>
<h3 id="gcp-role-types">GCP role types</h3>
<pre><code class="" data-line="">┌──────────────────┬──────────────────────────────┬──────────────────────────────────┐
│ Type             │ Example                      │ When to use                      │
├──────────────────┼──────────────────────────────┼──────────────────────────────────┤
│ Basic/Primitive  │ roles/editor, roles/owner    │ Never in production              │
│ Predefined       │ roles/storage.objectViewer   │ Default — service-specific       │
│ Custom           │ Your org defines             │ When predefined is too broad     │
└──────────────────┴──────────────────────────────┴──────────────────────────────────┘
</code></pre>
<p><code class="" data-line="">roles/editor</code> at the project level grants write access to almost every GCP service. I&#8217;ve seen it granted &#8220;temporarily&#8221; and found it attached six months later. Always use predefined roles.</p>
<pre><code class="" data-line=""># Find the right predefined role
gcloud iam roles list --filter=&quot;name:roles/storage&quot; --format=&quot;table(name,title)&quot;

# See exactly what permissions it includes
gcloud iam roles describe roles/storage.objectViewer

# Create a custom role when predefined is still too broad
cat &gt; custom-log-reader.yaml &lt;&lt; &#039;EOF&#039;
title: &quot;Log Reader&quot;
description: &quot;Read application logs — nothing else&quot;
stage: &quot;GA&quot;
includedPermissions:
  - logging.logEntries.list
  - logging.logs.list
  - logging.logMetrics.get
EOF
gcloud iam roles create LogReader --project=my-project --file=custom-log-reader.yaml
</code></pre>
<h3 id="azure-built-in-and-custom-roles">Azure built-in and custom roles</h3>
<pre><code class="" data-line=""># List built-in roles containing &quot;Storage&quot;
az role definition list --output table | grep Storage

# View what a built-in role grants
az role definition list --name &quot;Storage Blob Data Reader&quot;

# Create a custom role
az role definition create --role-definition custom-app-storage.json

# Assign at a specific scope
az role assignment create \
  --assignee alice@company.com \
  --role &quot;Storage Blob Data Reader&quot; \
  --scope /subscriptions/SUB_ID/resourceGroups/rg-prod/providers/\
Microsoft.Storage/storageAccounts/prodstore
</code></pre>
<hr />
<h2 id="rbac-vs-abac-which-access-control-model-to-use">RBAC vs ABAC: Which Access Control Model to Use</h2>
<h3 id="rbac-role-based-access-control">RBAC — Role-Based Access Control</h3>
<p>The dominant model. Access flows from role membership:</p>
<pre><code class="" data-line="">alice     ∈ BackendDeveloper  →  s3:GetObject on app-data/*
bob       ∈ DataAnalyst       →  athena:* on analytics-queries
ci-runner ∈ DeployRole        →  ecr:PutImage, ecs:UpdateService
</code></pre>
<p>RBAC degrades two ways: <strong>role explosion</strong> (200 roles, nobody can explain what they all do) and <strong>coarse roles</strong> (avoid explosion by making roles broad, now BackendDeveloper has prod access with no distinction from dev). Both look the same on a spreadsheet — lots of access, no clear principle.</p>
<h3 id="abac-attribute-based-access-control">ABAC — Attribute-Based Access Control</h3>
<p>ABAC grants access based on attributes of the principal, resource, or environment — not role membership. This one policy replaced 12 team-specific policies in one account:</p>
<pre><code class="" data-line="">{
  &quot;Effect&quot;: &quot;Allow&quot;,
  &quot;Action&quot;: &quot;ec2:*&quot;,
  &quot;Resource&quot;: &quot;*&quot;,
  &quot;Condition&quot;: {
    &quot;StringEquals&quot;: {
      &quot;aws:ResourceTag/Team&quot;: &quot;${aws:PrincipalTag/Team}&quot;
    }
  }
}
</code></pre>
<p>An engineer tagged <code class="" data-line="">Team=Platform</code> can only act on EC2 resources tagged <code class="" data-line="">Team=Platform</code>. Add a new team — tag their resources and their identity. No new policy. No new role.</p>
<p>The risk is tag drift. If someone tags a resource incorrectly, the access model breaks silently. In practice, I use ABAC for environment and team scoping, and explicit policies for sensitive services like KMS and IAM. How these primitives combine in a full AWS account is covered in the <a href="/aws-iam-deep-dive/">AWS IAM deep dive</a>.</p>
<h3 id="conditions-when-context-determines-access">Conditions — when context determines access</h3>
<pre><code class="" data-line="">// Require MFA for any IAM or Organizations action
{
  &quot;Effect&quot;: &quot;Deny&quot;,
  &quot;Action&quot;: [&quot;iam:*&quot;, &quot;organizations:*&quot;],
  &quot;Resource&quot;: &quot;*&quot;,
  &quot;Condition&quot;: { &quot;BoolIfExists&quot;: { &quot;aws:MultiFactorAuthPresent&quot;: &quot;false&quot; } }
}

// Restrict to corporate IP range
{
  &quot;Effect&quot;: &quot;Deny&quot;,
  &quot;Action&quot;: &quot;*&quot;,
  &quot;Resource&quot;: &quot;*&quot;,
  &quot;Condition&quot;: {
    &quot;NotIpAddress&quot;: { &quot;aws:SourceIp&quot;: [&quot;10.0.0.0/8&quot;, &quot;172.16.0.0/12&quot;] }
  }
}
</code></pre>
<p>The MFA condition is in every account I manage. A compromised API key without an MFA session can&#8217;t escalate IAM privileges — the Deny blocks it at the condition level. This single statement meaningfully reduces the blast radius of a credential compromise.</p>
<hr />
<h2 id="production-gotchas"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Production Gotchas</h2>
<pre><code class="" data-line="">╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 1 — Policies attached directly to users                 ║
║                                                                      ║
║  Feels fast. Creates the exact problem from 2015: access scattered  ║
║  across individual user objects with no shared structure.            ║
║  When the user leaves, their policies don&#039;t follow — they stay.     ║
║                                                                      ║
║  Fix: always use roles. Attach policies to roles. Assign roles to   ║
║  users. The role outlives the person.                               ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 2 — Using AWS managed policies in production            ║
║                                                                      ║
║  AmazonS3FullAccess grants s3:* on *. For a Lambda that reads one  ║
║  specific bucket, that&#039;s ~30 permissions you didn&#039;t need, all live. ║
║                                                                      ║
║  Fix: create customer managed policies scoped to the specific       ║
║  actions and ARNs the workload actually uses.                       ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 3 — iam:PassRole with &quot;Resource&quot;: &quot;*&quot;                   ║
║                                                                      ║
║  iam:PassRole lets an identity assign a role to an AWS service.     ║
║  With Resource: *, it can pass ANY role — including ones with more  ║
║  permissions than it currently has. That is a privilege escalation. ║
║                                                                      ║
║  Fix: always scope iam:PassRole to a specific role ARN:             ║
║  &quot;Resource&quot;: &quot;arn:aws:iam::ACCOUNT:role/SpecificRoleName&quot;          ║
╚══════════════════════════════════════════════════════════════════════╝

╔══════════════════════════════════════════════════════════════════════╗
║  &#x26a0;  GOTCHA 4 — Permissions boundary ≠ policy grant                 ║
║                                                                      ║
║  Setting a boundary that allows s3:* does NOT grant S3 access.     ║
║  The boundary is a ceiling — it limits maximum possible permissions. ║
║  The identity-based policy still needs to explicitly Allow the      ║
║  action. Both must be present for the access to work.               ║
╚══════════════════════════════════════════════════════════════════════╝
</code></pre>
<hr />
<h2 id="cross-cloud-rosetta-stone">Cross-Cloud Rosetta Stone</h2>
<p>Same concepts, different names and different directions. Bookmark this table.</p>
<pre><code class="" data-line="">┌─────────────────────────┬──────────────────────────┬──────────────────────────┬──────────────────────────┐
│ Concept                 │ AWS                      │ GCP                      │ Azure                    │
├─────────────────────────┼──────────────────────────┼──────────────────────────┼──────────────────────────┤
│ Atomic permission       │ s3:GetObject             │ storage.objects.get      │ .../blobs/read           │
│ Permission document     │ Policy (JSON)            │ (built into role def)    │ Role Definition          │
│ Access grant            │ Policy attachment        │ IAM Binding              │ Role Assignment          │
│ Job-function identity   │ IAM Role                 │ Predefined Role          │ Built-in Role            │
│ Non-human identity      │ IAM Role (assumed)       │ Service Account          │ Managed Identity         │
│ Org-level guardrail     │ SCP                      │ Org Policy               │ Management Group Policy  │
│ Permission ceiling      │ Permissions Boundary     │ —                        │ —                        │
│ Session restriction     │ Session Policy           │ —                        │ —                        │
│ Attribute-based grant   │ Tag conditions in policy │ IAM Conditions           │ Conditions in assignment │
└─────────────────────────┴──────────────────────────┴──────────────────────────┴──────────────────────────┘
</code></pre>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line="">┌──────────────────────────┬────────────────────────────────────────────────────────────┐
│ Term                     │ What it is                                                 │
├──────────────────────────┼────────────────────────────────────────────────────────────┤
│ Permission               │ Atomic: one action on one resource class                   │
│ Policy                   │ Document grouping permissions + conditions                 │
│ Role (AWS)               │ Assumable identity — carries policies, issues temp creds   │
│ Trust policy (AWS)       │ Who can assume this role — separate from permissions       │
│ Permissions boundary     │ Ceiling — limits max possible permissions; does not grant  │
│ SCP                      │ Org guardrail — constrains all identities in scope         │
│ IAM Binding (GCP)        │ Maps a role to a member on a specific resource             │
│ Role Assignment (Azure)  │ Grants a role definition at a specific scope               │
│ ABAC                     │ Access by tag/attribute — one policy replaces many roles   │
│ RBAC                     │ Access by role membership — clean until roles proliferate  │
│ iam:PassRole             │ Privilege escalation vector — always scope to specific ARN │
└──────────────────────────┴────────────────────────────────────────────────────────────┘

Commands to know:
┌────────────────────────────────────────────────────────────────────────────────┐
│  # AWS — list policies attached to a role                                     │
│  aws iam list-attached-role-policies --role-name MyRole                       │
│                                                                                │
│  # AWS — view what a managed policy actually grants                           │
│  aws iam get-policy-version \                                                  │
│    --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \              │
│    --version-id v1                                                             │
│                                                                                │
│  # AWS — who can assume this role?                                            │
│  aws iam get-role --role-name MyRole --query &#039;Role.AssumeRolePolicyDocument&#039;  │
│                                                                                │
│  # GCP — view the IAM policy on a project                                    │
│  gcloud projects get-iam-policy PROJECT_ID --format=json                      │
│                                                                                │
│  # GCP — list all roles and what permissions they include                    │
│  gcloud iam roles describe roles/storage.objectViewer                         │
│                                                                                │
│  # Azure — list role assignments in a subscription                           │
│  az role assignment list --all --output table                                 │
│                                                                                │
│  # Azure — view exactly what a built-in role grants                          │
│  az role definition list --name &quot;Storage Blob Data Reader&quot;                   │
└────────────────────────────────────────────────────────────────────────────────┘
</code></pre>
<hr />
<h2 id="framework-alignment">Framework Alignment</h2>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Reference</th>
<th>What It Covers Here</th>
</tr>
</thead>
<tbody>
<tr>
<td>CISSP</td>
<td>Domain 5 — Identity and Access Management</td>
<td>RBAC and ABAC are the implementation models for authorization at scale</td>
</tr>
<tr>
<td>CISSP</td>
<td>Domain 1 — Security &amp; Risk Management</td>
<td>Role design implements separation of duties and least privilege</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.15 Access control</td>
<td>Access control policy — roles and policies are the mechanism</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>5.18 Access rights</td>
<td>Provisioning, review, and removal of access rights — roles make this auditable</td>
</tr>
<tr>
<td>ISO 27001:2022</td>
<td>8.2 Privileged access rights</td>
<td>Permissions boundaries and conditions applied to elevated access</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.1</td>
<td>Logical access security — policy documents are the technical implementation</td>
</tr>
<tr>
<td>SOC 2</td>
<td>CC6.3</td>
<td>Access revocation — role-based model makes removal consistent and auditable</td>
</tr>
</tbody>
</table>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li>Permissions are atomic — one action on one resource class. Policies group permissions. Roles carry policies for assignment</li>
<li>AWS roles have two required configs: trust policy (who can assume) and permission policy (what it can do) — both must be correct</li>
<li>GCP binds roles to resources; AWS attaches policies to identities — the mental model runs in opposite directions</li>
<li>Azure separates role definition (what) from role assignment (who, where) — define once, assign at multiple scopes</li>
<li>RBAC scales through role design; ABAC scales through tag/attribute conditions — use ABAC where roles would proliferate</li>
<li><code class="" data-line="">iam:PassRole</code> and <code class="" data-line="">iam.serviceAccounts.actAs</code> are privilege escalation vectors — scope them to specific ARNs, never <code class="" data-line="">*</code></li>
<li>Conditions add context (MFA, IP, tags, time) to policies — the MFA condition on IAM actions is essential in every account</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p>EP04 goes deep on AWS IAM — the most complex of the three cloud models. Policy evaluation order, cross-account trust, permissions boundaries in practice, SCPs, and IAM Identity Center for human access. We&#8217;ll work through the patterns that make AWS IAM maintainable at production scale.</p>
<p><em>Next: <a href="/aws-iam-deep-dive/">AWS IAM Deep Dive: Users, Groups, Roles, and Policies Explained</a></em></p>
<p>Get the AWS IAM deep dive in your inbox when it publishes → <a href="https://linuxcent.com/subscribe">linuxcent.com/subscribe</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fiam-roles-policies-permissions-explained%2F&amp;linkname=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fiam-roles-policies-permissions-explained%2F&#038;title=IAM%20Roles%20vs%20Policies%3A%20How%20Cloud%20Authorization%20Actually%20Works" data-a2a-url="https://linuxcent.com/iam-roles-policies-permissions-explained/" data-a2a-title="IAM Roles vs Policies: How Cloud Authorization Actually Works"></a></p><p>The post <a href="https://linuxcent.com/iam-roles-policies-permissions-explained/">IAM Roles vs Policies: How Cloud Authorization Actually Works</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/iam-roles-policies-permissions-explained/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1458</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-08-31 11:46:11 by W3 Total Cache
-->