# Bookstore — Part 05 ch.01 "Authentication, authorization, RBAC".
#
# A DEDICATED ServiceAccount per workload + least-privilege RBAC. Numbered 05-
# so a whole-dir apply creates these AFTER the namespace (00-) and BEFORE the
# workloads (10-/11-/14-/20-…) that reference them via `serviceAccountName`.
#
# WHY a per-workload SA at all:
#   • The cluster's `default` SA in every namespace is shared and, by mistake,
#     often over-permissioned. A pod with no `serviceAccountName` silently uses
#     it. Giving every workload its OWN SA makes "who is this pod, in the API
#     server's eyes" explicit and auditable, and lets RBAC be scoped per app.
#   • The Bookstore Go services (catalog, orders, payments-worker) and the
#     stock images (postgres, redis, rabbitmq) and the DB batch Jobs
#     (db-migrate Job / cleanup CronJob) DO NOT call the Kubernetes API at all.
#     So the correct least privilege is *no API permissions and no mounted
#     token* — see `automountServiceAccountToken: false` below and the matching
#     field added to each workload's podSpec. A pod that never needs to talk to
#     kube-apiserver should not carry a credential that can.
#
# ONE SA PER WORKLOAD — the full set (no Bookstore Pod uses the `default` SA):
#   catalog-sa storefront-sa orders-sa payments-worker-sa postgres-sa redis-sa
#   rabbitmq-sa migrate-sa (the last shared by the db-migrate Job 21- AND the
#   cleanup CronJob 22-: both are postgres-image DB batch jobs with identical,
#   zero, API needs).
#   payments-worker-sa was ADDED in Part 06 ch.04 alongside
#   19-payments-worker-deploy.yaml: the worker only consumes RabbitMQ and serves
#   /metrics — it never calls kube-apiserver, so (like orders) it gets its own
#   identity with NO token mounted and NO Role/RoleBinding (deny-by-default).
#
# WHAT RBAC is granted:
#   • catalog-sa gets ONE tiny Role: `get` (not list/watch) on EXACTLY the
#     `catalog-config` ConfigMap by name (resourceNames). This is purely a
#     teaching example of resource-name-scoped least privilege — the app does
#     not actually use it (it reads config from injected env, kubelet-side).
#     It deliberately grants NOTHING on `secrets` (reading a Secret returns
#     cleartext — that is credential disclosure; ch.01/Part 03 ch.02).
#   • Every other SA gets NO Role/RoleBinding ⇒ deny-by-default: their token
#     (which is not even mounted) could do nothing anyway. That is the point.
#
# Token model (ch.01 "How it works under the hood"): modern Kubernetes injects
# a SHORT-LIVED, audience-bound, auto-rotated PROJECTED token via the
# TokenRequest API (not a static Secret). Setting automount=false on both the
# SA and the podSpec stops even that projection where it is unnecessary.
#
# Requires:
#   kubectl apply -f examples/bookstore/raw-manifests/00-namespace.yaml
# Apply (BEFORE the workloads that name these SAs):
#   kubectl apply -f examples/bookstore/raw-manifests/05-serviceaccounts-rbac.yaml
#   kubectl get sa,role,rolebinding -n bookstore
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: catalog-sa
  namespace: bookstore
  labels:
    app: catalog
    app.kubernetes.io/part-of: bookstore
# Defense in depth: even with the field set on the podSpec, default the SA to
# not auto-mount a token. The pod can still opt back in explicitly if it ever
# needs the API; nothing here does.
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: storefront-sa
  namespace: bookstore
  labels:
    app: storefront
    app.kubernetes.io/part-of: bookstore
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: orders-sa
  namespace: bookstore
  labels:
    app: orders
    app.kubernetes.io/part-of: bookstore
automountServiceAccountToken: false
---
# Part 06 ch.04: dedicated identity for the payments-worker (19-). The worker
# only consumes the RabbitMQ "orders" queue and exposes /metrics; it never
# calls kube-apiserver, so — exactly like orders-sa — it gets NO Role/
# RoleBinding (deny-by-default) and no auto-mounted token.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: payments-worker-sa
  namespace: bookstore
  labels:
    app: payments-worker
    app.kubernetes.io/part-of: bookstore
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: postgres-sa
  namespace: bookstore
  labels:
    app: postgres
    app.kubernetes.io/part-of: bookstore
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: redis-sa
  namespace: bookstore
  labels:
    app: redis
    app.kubernetes.io/part-of: bookstore
automountServiceAccountToken: false
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: rabbitmq-sa
  namespace: bookstore
  labels:
    app: rabbitmq
    app.kubernetes.io/part-of: bookstore
automountServiceAccountToken: false
---
# Shared by the db-migrate Job (21-) AND the cleanup CronJob (22-): both are
# postgres-image DB batch jobs that never call the Kubernetes API. One batch
# identity, no binding ⇒ deny-by-default, no token mounted.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: migrate-sa
  namespace: bookstore
  labels:
    app: db-migrate
    app.kubernetes.io/part-of: bookstore
automountServiceAccountToken: false
---
# catalog's least-privilege Role: GET (only) the single ConfigMap it owns,
# BY NAME. No list/watch (those ignore resourceNames and would expose every
# ConfigMap's existence), no secrets, no other resource. This is the smallest
# meaningful RBAC rule — a concrete "deny-by-default, allow one verb on one
# named object" example. apiGroups: [""] = the core API group (ConfigMap is
# core/v1, so the group string is empty).
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: catalog-config-reader
  namespace: bookstore
  labels:
    app: catalog
    app.kubernetes.io/part-of: bookstore
rules:
  - apiGroups: [""]                 # "" = core group (configmaps live here)
    resources: ["configmaps"]
    resourceNames: ["catalog-config"]   # ONLY this object, not all configmaps
    verbs: ["get"]                  # not list/watch (those bypass resourceNames)
---
# Bind that Role to catalog-sa ONLY. RoleBinding is namespaced: it grants the
# Role within `bookstore` to the listed subjects.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: catalog-config-reader-binding
  namespace: bookstore
  labels:
    app: catalog
    app.kubernetes.io/part-of: bookstore
subjects:
  - kind: ServiceAccount
    name: catalog-sa
    namespace: bookstore
roleRef:
  kind: Role                        # a Role (namespaced), not a ClusterRole
  name: catalog-config-reader
  apiGroup: rbac.authorization.k8s.io
# NOTE: storefront-sa, orders-sa, payments-worker-sa, postgres-sa, redis-sa,
# rabbitmq-sa and migrate-sa intentionally have NO binding. With no RoleBinding/
# ClusterRoleBinding, RBAC denies them everything — the correct posture for
# workloads that never call the API. (And with automountServiceAccountToken:
# false they hold no usable token regardless.) Only catalog-sa has the single
# narrow Role above, purely as the resource-name-scoped teaching example.
