This project provides a Kubernetes controller to synchronize RoleBindings and ClusterRoleBindings, used in Kubernetes RBAC, from group membership sources using consolidated configuration objects. The provided configuration objects allow you to define "virtual" groups that result in the creation of RoleBindings and ClusterRoleBindings that directly reference all users in the group.
Group membership can be declared directly in configuration objects or be pulled from an "upstream". Currently, the only supported upstream is GSuite, but we may add others, if required.
RBACSync leverages Custom Resource Definitions to manage binding configuration and group membership declaration. The CRDs are available in the deployment directory. Specific information about the type declarations are available in the the source code.
There are two CRDs defined depending on whether you want to create groups with cluster scope or namespace scope:
Custom Resource Definition | Scope | Description |
---|---|---|
RBACSyncConfig | Namespaced | Maps groups to users to create namespaced RoleBindings |
ClusterRBACSyncConfig | Cluster | Maps groups to users to create ClusterRoleBindings |
To define groups with RoleBindings within a namespace, you'll need to create an RBACSyncConfig. To define groups that create ClusterRoleBindings, you'll need to create ClusterRBACSyncConfig.
These two configuration objects can be used in concert to correctly configure
user bindings to Roles
and ClusterRoles
depending on the context.
If you are confused at any time, it might help to look at the example.yml for details.
The configurations used for RBACSync are centered around a bindings
entry. It
declares a mapping of group name to RoleBinding
or ClusterRoleBinding
. We
can use a very simple configuration to show this concept:
apiVersion: rbacsync.getcruise.com/v1alpha
kind: ClusterRBACSyncConfig
metadata:
name: example-simple
spec:
# Defines the roleRef to use against the subjects defined above.
bindings:
- group: mygroup-admin@getcruise.com
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
The above is a configuration for the ClusterRBACSyncConfig
with a single
binding entry that will map the Google Group "mygroup-admin@getcruise.com". For
each binding entry, a ClusterRoleBinding
will be created. It will look
similar to the following:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbacsync.getcruise.com/group: mygroup-admin@getcruise.com
labels:
owner: example-simple # allows use to select child objects quickly
name: example-simple-mygroup-admin@getcruise.com-cluster-admin
ownerReferences: # will be owned by the above configuration
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
# Users that are a member of the group, from the upstream
- apiGroup: rbac.authorization.k8s.io
kind: User
name: a@getcruise.com
- apiGroup: rbac.authorization.k8s.io
kind: User
name: b@getcruise.com
- apiGroup: rbac.authorization.k8s.io
kind: User
name: c@getcruise.com
The above will be created when the configuration is added to the Kubernetes
cluster. If the upstream group definition changes, updates will be picked up
after a configurable polling period. If the configuration that defined this, in
this case example-simple
, is removed, this ClusterRoleBinding
will be
automatically removed.
For namespace-scoped RBACSyncConfig
, the behavior is nearly identical except
for the following:
RBACSyncConfig
must be defined with a namespace.- All
RoleBindings
created as a result of theRBACSyncConfig
will be in the same namespace.
RBACSyncConfig
may reference a ClusterRole
to grant permissions to
namespaced resources defined in the ClusterRole
within the RoleBinding
’s
namespace. This allows administrators to define a set of common roles for the
entire cluster, then reuse them within multiple namespaces.
When deciding between using the two, you should mostly only need to look at
whether your assigning ClusterRoles
or Roles
and then use the equivalent
configuration. Refer to the Kubernetes RBAC documentation
for further guidance on that topic.
Group memberships allow one to specify the targets of the RoleBindings
and
ClusterRoleBindings
used in a configuration object. Abstractly, we'd like to
know based on a group name, what subjects are a member of that group. If you
are using an upstream, such as GSuite, these memberships are drawn directly
from group memberships define there. There may be cases when you want to store
these definitions directly in the cluster or need to augment groups with
additional members, such as GCP service accounts.
Each config has a spec which defines memberships
, calling out each member of
the group. The groups defined in memberships
may then be referenced in one
or mores bindings
.
To add memberships, simply include a memberships section in the spec, memberships:
memberships:
- group: cluster-admin-group
subjects:
- kind: User
name: a@getcruise.com
apiGroup: rbac.authorization.k8s.io
- kind: User
name: b@getcruise.com
apiGroup: rbac.authorization.k8s.io
- kind: User
name: c@getcruise.com
- group: someother-group
subjects:
- kind: User
name: a@getcruise.com
apiGroup: rbac.authorization.k8s.io
The above declares two groups, "cluster-admin-group" and "someother-group",
each with a list of subjects. When creating RoleBindings
, the subjects
declared here will be used as the subjects in the RoleBinding
object that
gets created.
NOTE: If you're using GSuite to configure group memberships, you likely won't need this section. However, it may be useful to add supplementary members or robot accounts to groups using memberships.
For the most part, you can start using RBACSync with the default deployment,
defined in deploy/20-deployment.yml
. This will
allow you to deploy configurations with bindings and memberships.
For reference, here are the available flags on the rbacsync
binary:
Usage of bin/rbacsync:
-alsologtostderr
log to standard error as well as files
-debug-addr string
bind address for liveness, readiness, debug and metrics (default ":8080")
-gsuite.credentials string
path to service account token json for gsuite access
-gsuite.delegator string
Account that has been delegated for use by the service account.
-gsuite.enabled
enabled membership definitions from gsuite
-gsuite.pattern string
Groups forwarded to gsuite account must match this pattern
-gsuite.timeout duration
Timeout for requests to gsuite API (default 30s)
-kubeconfig string
(optional) absolute path to the kubeconfig file (default "/home/sday/.kube/config")
-log_backtrace_at value
when logging hits line file:N, emit a stack trace
-log_dir string
If non-empty, write log files in this directory
-logtostderr
log to standard error instead of files
-stderrthreshold value
logs at or above this threshold go to stderr
-upstream-poll-period duration
poll period for upstream group updates (default 5m0s)
-v value
log level for V logs
-version
show the verson and exit
-vmodule value
comma-separated list of pattern=N settings for file-filtered logging
In configuring upstreams, you'll likely have to add one or more flags. Note that you can also increase logging granularity, if you need more in depth debugging.
To use GSuite, you'll need a service account with "G Suite Domain-Wide Delegation of Authority". It's recommended to read the guide to understand how this works in case you run into issues. The blog Navigating the Google Suite Directory API may also provide some insight.
The goal is to end up with two accounts:
- A "robot" GSuite account that acts as a "delagator" to the service account.
- A "service account" in a GCP project that will act as a delegate.
We then use the credentials of the service account with rbacsync
to allow it
to read the GSuite Directory API.
NOTE: You may be able to get away with delegating to a user account and taking it through the OAuth flow to delegate permissions. It is recommended to have a robot account with restricted permissions to control access and avoid lockouts if a user account experiences problems.
To setup this account, you'll need to take the following steps:
-
Create a new GSuite account with readonly access to the API on Google Groups. We'll call this the "robot" account. It should have the following permissions:
- https://www.googleapis.com/auth/admin.directory.group.readonly
- https://www.googleapis.com/auth/admin.directory.group.member.readonly
Please refer to the GSuite documentation, as the exact process for doing this may have changed.
-
Create a GCP service account in the GCP project where you're interested in using the GSuite "robot" account. Enable G Suite Domain-wide Delegation and note the Client ID.
-
Using the "security" component in the "admin.google.com" console, use the Client ID for the service account and add the following scopes, which are the same as those from step 1:
-
Generate the service account credentials. Make sure to save the generated JSON file somewhere safe.
Once we have the account setup we can modify the deployment to allow rbacsync
to use it. The first step is creating the secret from the service account
credentials created in step 5:
kubectl create secret generic --from-file=token.json=<my service account credentials path> rbacsync-gsuite-credentials
We can then apply the following diff to the rbacsync
deployment
to use the secret created above:
@@ -14,6 +14,10 @@
spec:
restartPolicy: Always
serviceAccountName: rbacsync
+ volumes:
+ - name: rbacsync-gsuite-credentials
+ secret:
+ secretName: rbacsync-gsuite-credentials
containers:
- name: rbacsync
image: cruise/rbacsync:latest
@@ -24,6 +28,14 @@
- /bin/rbacsync
args:
- "-logtostderr"
+ - "-gsuite.enabled"
+ - "-gsuite.credentials"
+ - "/run/secrets/gsuite/token.json"
+ - "-gsuite.delegator"
+ - "my-rbacsync-robot@getcruise.com"
+ volumeMounts:
+ - mountPath: /run/secrets/gsuite
+ name: rbacsync-gsuite-credentials
imagePullPolicy: Always
ports:
- name: debug-port
In the above, we set several flags to pass to rbacsync
:
-gsuite.enabled
: enables the GSuite upstream for groups.-gsuite.credentials
: path to the credentials generated for the service account. We map them to the secret volume mount location above.-gsuite.delegator
: This is the delegator whom the service account acts on behalf of. We call this the "robot" account in the instructions above. Be sure to set the argument to whatever account you use.
NOTE: You can also use
-gsuite.pattern
to limit which group names are sent upstream for queries. That takes a regular expression that must be matched before it will be allowed to query the upstream.
Once the above is configured and deployed, groups referenced in bindings
will
be automatically queried against GSuite. If memberships
have the same name as
an upstream group, the subject lists from the upstream and the memberships
section will be merged.
WARNING: Before running any commands here, make sure you are in the right cluster context. This will deploy to whatever cluster kubectl is currently configured for.
You can deploy the rbacsync with the following command:
kubectl apply -f deploy/
Once it has been deployed, you'll need to push a config to specify group members using the CRDs defined in this project. There are two CRDs defined depending on whether you want to create groups with cluster scope or namespace scope. To define groups with RoleBindings within a namespace, you'll need to create an RBACSyncConfig. To define groups that create ClusterRoleBindings, you'll need to create ClusterRBACSyncConfig.
If the RBACSyncConfig changes, the associated RoleBindings will be updated to reflect the difference. For example, if new group members are added, the RoleBinding will be updated with the new subjects.
If you remove RBACSyncConfig or ClusterRBACSyncConfig, any associated RoleBindings or ClusterRoleBindings will be removed, as well.
Building is easy to do. Make sure to setup your local environment according to https://golang.org/doc/code.html. Once setup, you should be able to build the binaries using the following command:
make
Generated binaries will then be available in bin/
. See the section on local
development for how to use the binary.
For developing on rbacsync, you can run inside or outside the cluster. For changes with lots of iteration, it is probably best to run it locally, since building and pushing an image to a deployment can be time consuming. The process would use the following commands:
WARNING: Before running any commands here, make sure you are in the right cluster context. This will deploy to whatever cluster kubectl is currently configured for.
kubectl apply -f deploy/10-deployment.yml
make
bin/rbacsync -logtostderr
The above will use your local kube context to run rbacsync, so you'll need
cluster-admin
Role for this to work. rbacsync will fire log messages and
events complaining about permissions if it cannot create the specified roles.
When you run the process, nothing will happen unless it has CRs to operate one. You can use the example.yml to try it out:
kubectl apply -f example.yml
You'll know its working if you see log messages indicating that it saw the configs.
If you are working on changes that require rbacsync to be running in a cluster,
such as when checking whether operations will work correctly, you can use the
deploy
target. To test in a cluster, the following can be run:
make REGISTRY=cruise/ deploy
The above will build the image, push it to a registry, apply the CRDs and deployment to the kubernetes context and update the deployment to use the created image. This makes iterating on testing within a cluster much easier.
You'll need cluster admin to perform this operation.
RBACSync is versioned using semantic versioning. For the most part, patch releases should be only bug fixes, minor releases can have backwards compatible feature introductions and major releases are for breaking changes. If you can't decide whether a feature or change is breaking, err on the side of caution when incrementing the version number and just do it.
The release process for rbacsync is triggered with tags. Every build in master will pickup the tag and create a version number relative, using a git commitish, to the tag and push it to the registry. For production, you should only use exact tags, and, if possible, only proper releases, meaning no release candidates, alphas or betas.
To perform a release, you simply need to push a tag to the repository. To create a tag, run the following, with the new version number of course:
git tag -s v0.1.0
Notice that the version number is always prefixed with a v
when it appears
as a tag. This allows us to easily identify version tags with a text prefix. We
also sign the tag with a PGP key. If you haven't already done so, it is
recommended to generate a PGP key for your workstation and add it to github.
When you run this command, an editor will pop up to include a release message.
The following format is usually sufficient:
rbacsync 0.1.0
With this release, we introduce support for Google Groups API with
GSuite Directory SDK. This allows one to configure groups without
declared memberships that are queried to the GSuite upstream resource.
There may be releases with complex information for upgrades and caveats, so feel free to be verbose. Once you are ready, save in the editor and the tag will be created. To push it to github, run the following command:
git push --tags origin v0.1.0
This will push the tag remotely, and start the build and push for rbacsync. Make sure to check in CircleCI to ensure it was triggered.
In addition to pushing this tag, it is also recommended to create a release in the releases page. Make sure to name it consistently with other releases. It should be sufficient to use the exact same text from the tag, but you may have to tweak the formatting to be compatible with markdown.
While this is an extra step when releasing the software, it is very helpful when looking at a project to see its releases properly documented in github. Also follow this practice to have a healthy project!
Copyright 2018 Cruise LLC
Licensed under the Apache License Version 2.0 (the "License"); you may not use this project except in compliance with the License.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Contributions are welcome! Please see the agreement for contributions in CONTRIBUTING.md.
Commits must be made with a Sign-off (git commit -s
) certifying that you
agree to the provisions in CONTRIBUTING.md.