Skip to main content

RoleBindings

A RoleBinding is where access actually starts. It takes a Role and gives it to someone: a person, a team, or a playbook or notification that acts on its own.

Think of the Role as a job description and the RoleBinding as handing that job to people. You write a Role such as "staging operator" once, then create a RoleBinding for each team that does that job.

A first RoleBinding​

This is the RoleBinding Mission Control ships to make every member of the admins team an admin:

admins-binding.yaml
apiVersion: mission-control.flanksource.com/v1
kind: RoleBinding
metadata:
name: admins
namespace: mission-control-builtin
spec:
description: Members of the admins team are admins
role: admin
subjects:
teams:
- admins

Every installation has this binding. You can't create your own bindings in mission-control-builtin, but it shows the two parts every RoleBinding has:

  • role names the Role to give. Here it's a bare name, admin, because the Role is in the same namespace as the binding. A Role in another namespace is written as namespace/name, such as mission-control-builtin/viewer.
  • subjects says who gets it.

A RoleBinding can also have a description, a constraint and deny, which are covered below.

Who you can give a Role to​

People​

List people by email under subjects.people. This binding gives John read access to every config:

john-catalog-read.yaml
apiVersion: mission-control.flanksource.com/v1
kind: Scope
metadata:
name: john-catalog-read
namespace: default
spec:
targets:
- config:
name: "*"
---
apiVersion: mission-control.flanksource.com/v1
kind: Role
metadata:
name: john-catalog-read
namespace: default
spec:
description: Grant John read permissions to catalogs
rules:
- name: read-configs
action: read
resource:
scopeRef: john-catalog-read
---
apiVersion: mission-control.flanksource.com/v1
kind: RoleBinding
metadata:
name: john-catalog-read
namespace: default
spec:
role: john-catalog-read
subjects:
people:
- john@doe.com

Use * as the only entry to give a Role to everyone who signs in, which is the usual way to give everyone the shipped viewer Role.

Users of an external identity provider are named as <provider>:<username>, and * doesn't include them. To give a Role to every user of one provider, use <provider>:*.

Teams​

List team names under subjects.teams. Teams are created and managed from Settings → Teams. Everyone in the team gets the Role, so access follows team membership: add someone to the team and they get it, remove them and they lose it.

Binding Roles to teams, rather than to individual people, is usually easier to manage. You can also keep team membership in sync with your login provider's groups. See Team Mapping.

Playbooks, notifications and other automation​

Starting a playbook run is checked as the person who starts it: they need playbook:run on the playbook, and read on the config it runs on. Everything the run then does, such as reading configs, using connections or running other playbooks, is checked as the playbook. A notification works the same way when it's sent. So when a playbook needs to call an API through a connection, the playbook itself needs a RoleBinding:

playbook-connection.yaml
apiVersion: mission-control.flanksource.com/v1
kind: Scope
metadata:
name: allow-playbook-connection
namespace: default
spec:
targets:
- connection:
name: httpbin
namespace: mc
---
apiVersion: mission-control.flanksource.com/v1
kind: Role
metadata:
name: allow-playbook-connection
namespace: default
spec:
description: allow playbook mc/http-authorized to use connection mc/httpbin
rules:
- name: use-connections
action: connection:use
resource:
scopeRef: allow-playbook-connection
---
apiVersion: mission-control.flanksource.com/v1
kind: RoleBinding
metadata:
name: allow-playbook-connection
namespace: default
spec:
role: allow-playbook-connection
subjects:
playbooks:
- name: http-authorized
namespace: mc

Here the subject is the http-authorized playbook in the mc namespace, and the Role lets it use the httpbin connection. Whoever runs the playbook doesn't need access to the connection themselves.

You can select playbooks, notifications, scrapers, canaries, topologies and plugins this way, each by namespace and name. Use name: "*" on its own to match every resource of that kind in every namespace, or set only namespace to match everything in that namespace. Here plugins means Mission Control plugins that add operations to the catalog, not config scraper plugins.

Permissions for automation​

When this happensBind a Role toWith
A playbook uses a connectionThe playbookconnection:use on the connection
A playbook reads configs, for example in a templateThe playbookread on the configs
A playbook runs another playbookThe first playbookplaybook:run on the second playbook, and read on its target
A notification sends through a connectionThe notificationconnection:use on the connection
A notification runs a playbookThe notificationplaybook:run on the playbook, and read on the config it runs on
A scraper, canary or topology uses a connectionThe scraper, canary or topologyconnection:use on the connection
A plugin looks up a connectionThe pluginread or connection:use on the connection

See also Who a run acts as and Notification permissions.

warning

A binding by namespace also covers resources created there later. Anyone who can create a playbook in that namespace gets a playbook with the binding's access. For sensitive Roles, such as using a connection that pages people, bind each playbook or notification by its exact namespace and name.

Limiting a Role to some resources​

Sometimes several teams do the same job on different resources. Team A operates tenant A's configs, and team B operates tenant B's. Instead of writing a Role per team, write one Role and add a constraint to each team's RoleBinding.

A constraint names a Scope, and narrows every rule of the Role to resources that are in both the rule's Scope and the constraint's Scope:

Constraint fieldNarrows
constraint.resource.scopeRefWhat the rules act on, such as the configs that may be read, or the playbooks that may be run
constraint.target.scopeRefWhat playbooks may be run against

For example, bind a shared production-operator Role to team A with both fields set to a tenant-a Scope. Team A can then read, and run playbooks on, only the production configs that are also in tenant-a. Team B's binding does the same with tenant-b.

constraint.resource also narrows the playbooks that playbook:run rules allow. So tenant-a must select tenant A's playbooks as well as its configs, or team A can't run any playbook. One Scope can select several types, as described in Combining targets.

Some tips for constraints:

  • Set both resource and target when the Role runs playbooks. Running a playbook on a config also needs read on that config, and a read rule is only narrowed by resource.
  • Check AllRulesApply. A constraint can't narrow every kind of rule. For example, creating and deleting are always granted for a whole type. Those rules are left out of the binding rather than granted in full, and the binding's AllRulesApply condition, which kubectl describe shows, names each one. If a team gets less than you expected, look there first.
  • To narrow rules differently, use separate Roles. A constraint applies to the whole Role.

Refusing a Role​

Set deny: true on a RoleBinding to refuse its Role instead of granting it. A deny wins over everything else, including admin. For example, take a read-secrets Role with a read rule on a Scope of configs tagged secret=true. A deny binding of that Role to the contractors team keeps contractors from reading those configs, even if someone later binds them to a broader Role by mistake.

Use deny bindings as a safety net, not as the main way of controlling access. Because nothing is allowed by default, the simplest way to keep something from someone is not to grant it. Keep these limits in mind:

  • A broken deny binding denies nothing. If its Role or Scope is missing, the binding shows Ready=False and has no effect. Check its status after you create it.
  • A deny only blocks actions, not the data itself. Denying read on some configs doesn't remove them from a view that shows them, or stop a playbook the person may run from reading them. Deny the view as well.
  • A deny can't lock admins out. A deny binding is ignored for actions on RoleBindings, so admins can always remove it.

Checking a RoleBinding​

Every RoleBinding has a Ready status. Ready=False means it isn't granting anything yet, for example because its Role doesn't exist. It starts working as soon as the problem is fixed, so you can create a RoleBinding before its Role.

To see what someone can actually do, open the Subjects page under Settings. It explains which rules match a given person and action.

warning

Anyone who can create or change RoleBindings can give anyone, themselves included, any Role. Only let people you'd trust as admins write Scopes, Roles and RoleBindings, in the UI or in Kubernetes. The namespace a RoleBinding lives in doesn't limit what it can grant.