Kubernetes RBAC is already a grammar: Roles, bindings, verbs, resources. OpenShift keeps that engine and adds a friendlier front door. When someone says “create a project for the payments team,” they mean a Namespace plus OpenShift project metadata — display name, description, optional annotations, default quotas, and sometimes automatic NetworkPolicy. When someone says “give them edit access,” they mean a ClusterRole binding that OpenShift documents as a role name most admins recognise.
If you arrive from plain Kubernetes, the overlap is reassuring and the differences matter. kubectl get ns and oc get projects show related but not identical views. oc policy and oc adm policy wrap RBAC operations platform teams use every day. ServiceAccounts still run your Pods, but default bindings and console integration assume OpenShift conventions.
This post covers the basics I wish teams aligned on before the first production incident: what a Project is, what the default roles actually allow, how ServiceAccounts fit in, how to grant access with oc adm policy, and how to test permissions without guessing.
Project vs Namespace — same room, different sign on the door
At the API level, an OpenShift Project is a Namespace. Every Project creates a Namespace with the same name. If the Project is payments-dev, the Namespace is payments-dev. Objects — Deployments, Services, Routes, Secrets — live in that Namespace exactly as in upstream Kubernetes.
What Project adds is a layer OpenShift manages:
- Display name and description for humans in the web console.
- ProjectRequest workflow so permitted users can self-provision within limits.
- Default annotations and labels applied by the platform or templates.
- Optional Project-level defaults such as ResourceQuota, LimitRange, or NetworkPolicy created at provisioning time.
- Visibility in
oc get projectswith status and requesting user metadata.
Compare the two views:
kubectl get namespace payments-dev -o yaml
oc get project payments-dev -o yaml
oc describe project payments-dev
For automation that must stay portable, targeting the Namespace is fine. For day-to-day OpenShift work, oc project payments-dev switches context the way kubectl config set-context --current --namespace=... does, with OpenShift-specific messaging if the Project does not exist or you lack access.
Create a Project (requires permission to create ProjectRequest or cluster admin):
oc new-project payments-dev --display-name="Payments development"
oc get projects
oc project payments-dev
Deleting a Project (oc delete project payments-dev) removes the Namespace and namespaced objects inside it — the same destructive scope as on vanilla Kubernetes. The word “project” sounds softer; the effect is not.
Important mental model: Project boundaries are RBAC and quota boundaries, not automatic network isolation. Two Projects do not magically block Pod-to-Pod traffic unless NetworkPolicy or platform defaults enforce it. Two Projects do not stop a cluster admin from seeing both. Treat a Project as an ownership and permissions scope first.
Default roles — view, edit, admin
OpenShift ships aggregated ClusterRoles exposed as view, edit, and admin (plus cluster-admin at cluster scope). These names appear in the web console when you “Add user to project.” Under the hood they are ClusterRoleBindings or RoleBindings to ClusterRoles such as view, edit, and admin.
view is read-only access to most namespaced workloads — logs and status without writes, usually without Secret contents. edit covers deploying and changing apps, Services, and ConfigMaps; it often includes Secrets, but verify in your cluster. admin adds project-level RBAC, quotas, and limit ranges for team leads. cluster-admin is platform scope only.
Exact rules evolve with OpenShift versions. Do not memorise every rule from a blog post. Inspect them:
oc describe clusterrole view
oc describe clusterrole edit
oc describe clusterrole admin
Grant a user edit on one Project:
oc adm policy add-role-to-user edit jane -n payments-dev
oc adm policy add-role-to-user view auditor -n payments-dev
oc adm policy add-role-to-user admin team-lead -n payments-dev
Grant a group from your identity provider:
oc adm policy add-role-to-group edit payments-developers -n payments-dev
List who has access:
oc adm policy who-can get pods -n payments-dev
oc adm policy who-can delete deployments -n payments-dev
oc get rolebinding -n payments-dev
oc describe rolebinding edit-0 -n payments-dev
view is the default starting point for someone who only needs to troubleshoot running workloads. edit is the default for engineers shipping Deployments and Routes. admin is for people who manage membership and namespace-level policy — not every developer needs it.
Common mistake: giving admin because “they need to see Secrets.” Sometimes edit already includes Secret read in your version; sometimes the correct answer is a custom Role that allows get secrets without full admin. Read the ClusterRole, then decide.
Another mistake: confusing OpenShift admin with cluster-admin. Project admin cannot safely reconfigure the entire cluster. cluster-admin should be rare human accounts, break-glass procedures, and automation with audited credentials.
ServiceAccounts in OpenShift Projects
Human users authenticate through the cluster identity provider. Workloads authenticate to the Kubernetes API as ServiceAccounts. Each Project gets a default ServiceAccount automatically. Pods that omit serviceAccountName use it.
oc get sa -n payments-dev
oc describe sa default -n payments-dev
oc describe sa builder -n payments-dev
OpenShift also creates ServiceAccounts such as builder and deployer for built-in image build and deployment flows. Custom apps should use named ServiceAccounts rather than overloading default.
Create a dedicated ServiceAccount for an application:
apiVersion: v1
kind: ServiceAccount
metadata:
name: api
namespace: payments-dev
Reference it in the Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: payments-dev
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
serviceAccountName: api
automountServiceAccountToken: true
containers:
- name: api
image: registry.example.org/payments/api:1.8.0
ports:
- containerPort: 8080
If the app never calls the Kubernetes API, consider automountServiceAccountToken: false after confirming the app and any sidecars do not need it. Operators and controllers usually need tokens; simple HTTP services often do not.
Grant a ServiceAccount permission inside the Project with a RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: api-reads-config
namespace: payments-dev
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: api-reads-config
namespace: payments-dev
subjects:
- kind: ServiceAccount
name: api
namespace: payments-dev
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: api-reads-config
Or with oc:
oc create role api-reads-config \
--verb=get,list,watch \
--resource=configmaps \
-n payments-dev
oc adm policy add-role-to-user api-reads-config \
system:serviceaccount:payments-dev:api \
-n payments-dev
Remember SCC bindings from the security post: ServiceAccounts also need permission to use an appropriate Security Context Constraint. RBAC for API access and SCC for Pod admission are separate checks. Fixing RoleBinding alone does not fix an SCC denial.
oc adm policy — grants, removals, and audits
Day-to-day OpenShift access management often goes through oc adm policy rather than hand-written YAML. Admins use it; developers should recognise the commands to request the right grant and to verify what exists.
Add roles to users or groups:
oc adm policy add-role-to-user view jane -n payments-dev
oc adm policy add-role-to-group edit payments-developers -n payments-dev
oc adm policy add-cluster-role-to-user cluster-admin break-glass-admin
Remove roles with oc adm policy remove-role-from-user and remove-role-from-group when people change teams. Audit who can perform an action:
oc adm policy who-can create deployments -n payments-dev
oc adm policy who-can get secrets -n payments-dev
oc adm policy who-can delete pods -n payments-dev
SCC-related helpers live here too:
oc adm policy add-scc-to-user restricted -z api -n payments-dev
oc adm policy who-can use scc restricted -n payments-dev
When documentation says “give the SA pull access,” that may mean attaching an image pull Secret to the ServiceAccount or configuring registry RBAC — related but not the same as project edit. Read the error: 403 Forbidden from the registry is not fixed by add-role-to-user edit.
Practical permissions testing
Guessing creates two failure modes: someone lacks access and opens a ticket for “full admin,” or someone has too much access and nobody notices until an audit. OpenShift inherits Kubernetes testing tools; use them before and after changing bindings.
Test the current user:
oc auth can-i get pods -n payments-dev
oc auth can-i create deployments -n payments-dev
oc auth can-i delete secrets -n payments-dev
oc auth can-i update routes -n payments-dev
Test as another user with impersonation (requires permission to impersonate):
oc auth can-i get pods -n payments-dev --as=jane
oc auth can-i get secrets -n payments-dev --as=jane
oc auth can-i create deployments -n payments-dev --as=jane
Test as a ServiceAccount:
oc auth can-i get configmaps -n payments-dev \
--as=system:serviceaccount:payments-dev:api
oc auth can-i list secrets -n payments-dev \
--as=system:serviceaccount:payments-dev:api
Add -v=6 to see which rule matched. Run checks after onboarding and role changes.
When can-i says yes but the console still fails, check group membership sync from LDAP or OIDC, caching delay, and whether the user switched to the correct Project with oc project. When can-i says no but you expected yes, describe the RoleBinding and ClusterRole — a common issue is binding edit in payments-dev while the user tests against payments-staging.
Avoid cluster-admin to unblock day-to-day work; prefer edit plus a targeted Role for the one missing verb. Name ServiceAccounts per app instead of binding permissions to default. Remember that Project scope is not network isolation — add NetworkPolicy when that matters — and that API RBAC and SCC are separate checks.
Final thought
OpenShift Projects do not replace Kubernetes RBAC — they package Namespaces for multi-team platforms. view, edit, and admin are readable defaults if you verify what your version actually grants. ServiceAccounts carry workload identity and SCC eligibility. oc adm policy and oc auth can-i turn access from folklore into something you can demonstrate.
The payoff is fewer midnight messages asking for “full access” and fewer incidents where a controller silently lacked one verb. Name the Project, name the role, test the binding, then go deploy.