# RBAC Manager turns a list of people and roles into bindings, and v1.10 made tags immutable

> RBAC Manager is an Apache-2.0 licensed Kubernetes operator that replaces hand-written RoleBindings and ServiceAccounts with custom resources describing desired state. It suits platform teams who grant access from a source of truth, and the registry migration, immutable signed tags and the conflict between declarative state and imperative grants are the details that decide adoption.

**FairwindsOps/rbac-manager** — A Kubernetes operator that simplifies the management of Role Bindings and Service Accounts.

- Repository: https://github.com/FairwindsOps/rbac-manager
- Website: https://fairwinds.com
- Stars: 1,674 · Forks: 133
- Language: Go
- License: Apache-2.0
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/fairwindsops-rbac-manager

## Desired state instead of hand-edited bindings

The problem this solves is stated in one sentence in the README: instead of managing role bindings or service accounts directly, you specify a desired state and the operator makes the necessary changes to achieve it.

RoleBindings and ServiceAccounts can otherwise be managed directly, making the desired state harder to review. Writing the same information as a custom resource makes it reviewable in the same way as any other manifest.

The repository shows what those definitions look like. The examples/ directory holds rbacdefinition-everything.yaml, which is presumably every field at once, rbacdefinition-users.yaml for granting access to people, and rbacdefinition-sa-imagepull.yaml for service accounts that need image pull permissions. The first is the one to read first when you are learning the resource shape, the other two are the two cases most teams actually have.

The design is a standard operator loop: a controller watches your custom resource, compares it with what exists, and reconciles. If you want the reasoning behind the shape rather than the usage, DESIGN.md and ROADMAP.md are both at the repository root.

## The v1.10 registry change breaks deployment manifests

The most consequential line in the README is a required action, and it is written as a diff because that is how people will hit it:

```diff
- quay.io/reactiveops/rbac-manager:<tag>
+ us-docker.pkg.dev/fairwinds-ops/oss/rbac-manager:<tag>
```

Starting with v1.10.0 the images moved to us-docker.pkg.dev/fairwinds-ops/oss/rbac-manager, and quay.io/reactiveops/rbac-manager is deprecated. Any manifest, Helm value or CI pipeline still pointing at the old repository needs updating before you upgrade.

The second half of that change is stricter and affects every team. Images are now signed, and tags are immutable, which means the floating tags are gone. Specifically v1, v1.9 and latest no longer exist. Instead you reference a full version tag:

```
us-docker.pkg.dev/fairwinds-ops/oss/rbac-manager:v<major>.<minor>.<patch>
```

Or you pin by digest, with us-docker.pkg.dev/fairwinds-ops/oss/rbac-manager@sha256:<digest>.

Immutable tags are the right call for a security-relevant controller and they are also a real change in operation: a mutable tag can be re-pointed during an incident, and this one cannot. If your automation relies on always pulling latest, that has to be rewritten before the upgrade.

## The container image is a static binary on Alpine running as nobody

The Dockerfile shows the runtime configuration.

```dockerfile
FROM alpine:3.22
USER 65534
COPY rbac-manager /
ENTRYPOINT ["/rbac-manager"]
```

There is no package installation, no shell tooling and no interpreter, because the Go build produces one static binary that is copied to the root of the image. The base is alpine:3.22, and the process runs as user 65534, which is nobody in Alpine rather than a named account.

The default command is --log-level=info, passed through CMD, so the operator logs at info unless you say otherwise. Since this is an operator with cluster-wide visibility over RBAC, the point of running unprivileged is straightforward: it needs the service account's permissions in the API and nothing from the local filesystem.

Version stamping is handled by GoReleaser through.goreleaser.yml, with a version/ directory at the root, and builds run through CircleCI configuration in.circleci/.

## Kubernetes 1.34 libraries on Go 1.25, with metrics wired in

The go.mod pins the stack precisely, which is the fastest way to judge whether your cluster version is in range.

The module requires Go 1.25.5, and it depends on k8s.io/api, k8s.io/apimachinery and k8s.io/client-go all at v0.34.3, with controller-runtime at v0.22.4. A client library at 0.34 is tied to a particular Kubernetes release generation, and an operator built against it is normally tested against that generation of cluster.

Prometheus client_golang v1.23.2 is a direct dependency rather than an indirect one, so the operator exports metrics out of the box. go-logr and klog provide logging, and testify is the test framework.

The rest of the go.mod is a long indirect block, much of it the go-openapi family that controller-runtime pulls in through the Kubernetes API machinery. Nothing in that list is unusual; it is what a controller-runtime operator looks like.

The build is driven by a Makefile whose default target is test, and that target is worth reading as a description of what the project considers done. It runs golangci-lint, then go test with a coverage profile, then go vet writing a report, and finally generates an HTML coverage report. Cross compilation is mentioned at the end of the file, consistent with the GoReleaser setup.

## The failure mode is a binding somebody else needs

Nothing in the README describes what happens when a binding exists that your definition does not describe, and that is the question to answer before deploying this to a cluster with real users.

The premise of the operator is that it makes the necessary changes to achieve the declared state. A controller that only ever added bindings would be safe to try and useless as a model. A controller that also removes what is not declared is the thing that makes drift self-healing, and it is also the thing that will delete a RoleBinding a colleague created by hand for an incident that has not ended.

That behaviour is inherent to the declarative model rather than a bug to report, and the migration story in the README does not cover it. The pragmatic approach is to inventory the existing bindings first, express the ones that must survive in a definition, and let the operator remove the rest.

The examples named rbacdefinition-users.yaml and rbacdefinition-sa-imagepull.yaml suggest the intended granularity is narrow, per group of people or per capability, which is what makes an inventory practical rather than an exercise in rewriting every binding by hand.

## Part of a cluster policy toolkit rather than a standalone tool

The README closes with the rest of Fairwinds' open source work, and the pairing is informative about where this fits.

Polaris audits, enforces and builds policies for Kubernetes resources, with more than twenty built-in checks for best practices. Goldilocks right-sizes deployments by comparing memory and CPU settings against actual usage. Pluto detects resources that have been deprecated or removed in future Kubernetes versions. Nova checks whether Helm charts have updates available.

Read together, that is a cluster hygiene set: audit, right-size, detect deprecations, update charts, and manage who can do what. RBAC Manager is the identity half of it, and its value is highest in a cluster where the other tools are already reporting.

There is also a fairwinds-insights.yaml at the repository root, which suggests cluster telemetry reporting is wired in by default rather than opt-in. That is worth checking against your own policy before installing, since a controller that reports cluster state somewhere is a different decision from one that does not.

Licensing is Apache-2.0, and the last push was on 2026-10-09 with v1.10.2 released on 2026-08-10.

## Conclusion

RBAC Manager fits a platform team whose access grants live in a database or a directory and whose cluster has accumulated hand-edited RoleBindings nobody is willing to own, because the operator makes the desired state reviewable in Git and re-applies it after drift. It is the wrong choice for a small cluster where a handful of static bindings is clearer than another controller, and it is wrong for anyone who cannot accept that bindings it manages will be corrected back to the definition. Before upgrading from a pre-1.10 release, rewrite your image references from quay.io/reactiveops/rbac-manager to us-docker.pkg.dev/fairwinds-ops/oss/rbac-manager and replace every floating tag with a full version tag, then check that no cluster automation still creates bindings the definition does not describe, because the operator will remove those.

## FAQ

### What does the RBAC Manager operator actually do?

It manages RBAC declaratively. Instead of creating RoleBindings and ServiceAccounts directly, you define the desired state in a custom resource and the operator makes the changes needed to reach it. The examples directory shows definitions for users and for service accounts needing image pull permissions.

### What changed in rbac-manager v1.10.0?

Images moved to us-docker.pkg.dev/fairwinds-ops/oss/rbac-manager and quay.io/reactiveops/rbac-manager is deprecated. Images are now signed and tags are immutable, so the floating tags v1, v1.9 and latest no longer exist; use a full version tag or pin by digest.

### Which Kubernetes and Go versions does rbac-manager build against?

go.mod requires Go 1.25.5 and depends on k8s.io/api, apimachinery and client-go at v0.34.3, with controller-runtime v0.22.4. The operator also depends on the Prometheus client library directly, so metrics are exported without an extra add-on.

### How do I run the rbac-manager tests?

The Makefile default target is test, which runs golangci-lint, then go test with a coverage profile in atomic mode, then go vet into govet-report.out, and finally generates cover-report.html from the coverage data.

### How is the rbac-manager container image built?

The Dockerfile uses alpine:3.22, copies the compiled binary to the image root, runs as user 65534 which is nobody in Alpine, and starts /rbac-manager with a default of --log-level=info. No packages are installed because the Go build produces a single static binary.

## Sources

- [FairwindsOps/rbac-manager on GitHub](https://github.com/FairwindsOps/rbac-manager)
- [License: Apache-2.0](https://github.com/FairwindsOps/rbac-manager/blob/master/LICENSE)
- [Project website](https://fairwinds.com)
- [README](https://github.com/FairwindsOps/rbac-manager/blob/master/README.md)
- [Releases](https://github.com/FairwindsOps/rbac-manager/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/fairwindsops-rbac-manager
