Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for rbac v1beta1 on kubernetes v1.7.5+ #682

Merged
merged 1 commit into from
Jul 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions pkg/backup/rbac.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
Copyright 2018 the Heptio Ark contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

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.
*/

package backup

import (
"github.com/pkg/errors"
rbac "k8s.io/api/rbac/v1"
rbacbeta "k8s.io/api/rbac/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
rbacclient "k8s.io/client-go/kubernetes/typed/rbac/v1"
rbacbetaclient "k8s.io/client-go/kubernetes/typed/rbac/v1beta1"
)

// ClusterRoleBindingLister allows for listing ClusterRoleBindings in a version-independent way.
type ClusterRoleBindingLister interface {
// List returns a slice of ClusterRoleBindings which can represent either v1 or v1beta1 ClusterRoleBindings.
List() ([]ClusterRoleBinding, error)
}

// noopClusterRoleBindingLister exists to handle clusters where RBAC is disabled.
type noopClusterRoleBindingLister struct {
}

func (noop noopClusterRoleBindingLister) List() ([]ClusterRoleBinding, error) {
return []ClusterRoleBinding{}, nil
}

type v1ClusterRoleBindingLister struct {
client rbacclient.ClusterRoleBindingInterface
}

func (v1 v1ClusterRoleBindingLister) List() ([]ClusterRoleBinding, error) {
crbList, err := v1.client.List(metav1.ListOptions{})
if err != nil {
return nil, errors.WithStack(err)
}
var crbs []ClusterRoleBinding
for _, crb := range crbList.Items {
crbs = append(crbs, v1ClusterRoleBinding{crb: crb})
}

return crbs, nil
}

type v1beta1ClusterRoleBindingLister struct {
client rbacbetaclient.ClusterRoleBindingInterface
}

func (v1beta1 v1beta1ClusterRoleBindingLister) List() ([]ClusterRoleBinding, error) {
crbList, err := v1beta1.client.List(metav1.ListOptions{})
if err != nil {
return nil, errors.WithStack(err)
}
var crbs []ClusterRoleBinding
for _, crb := range crbList.Items {
crbs = append(crbs, v1beta1ClusterRoleBinding{crb: crb})
}

return crbs, nil
}

// NewClusterRoleBindingListerMap creates a map of RBAC version strings to their associated
// ClusterRoleBindingLister structs.
// Necessary so that callers to the ClusterRoleBindingLister interfaces don't need the kubernetes.Interface.
func NewClusterRoleBindingListerMap(clientset kubernetes.Interface) map[string]ClusterRoleBindingLister {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be slightly better if this returned a map[string]ListerConstructor, so that you don't have to construct all of them upfront. This way, only the one that's actually selected is instantiated. I would just inline the constructors as anonymous functions/closures of the clientset argument.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a problem or cost with instantiating them all, given we have 3? To me, returning them directly simplifies reading the code and reduces the number of types we have to carry around.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, not a big deal.

return map[string]ClusterRoleBindingLister{
rbac.SchemeGroupVersion.Version: v1ClusterRoleBindingLister{client: clientset.RbacV1().ClusterRoleBindings()},
rbacbeta.SchemeGroupVersion.Version: v1beta1ClusterRoleBindingLister{client: clientset.RbacV1beta1().ClusterRoleBindings()},
"": noopClusterRoleBindingLister{},
}
}

// ClusterRoleBinding abstracts access to ClusterRoleBindings whether they're v1 or v1beta1.
type ClusterRoleBinding interface {
// Name returns the name of a ClusterRoleBinding.
Name() string
// ServiceAccountSubjects returns the names of subjects that are service accounts in the given namespace.
ServiceAccountSubjects(namespace string) []string
// RoleRefName returns the name of a ClusterRoleBinding's RoleRef.
RoleRefName() string
}

type v1ClusterRoleBinding struct {
crb rbac.ClusterRoleBinding
}

func (c v1ClusterRoleBinding) Name() string {
return c.crb.Name
}

func (c v1ClusterRoleBinding) RoleRefName() string {
return c.crb.RoleRef.Name
}

func (c v1ClusterRoleBinding) ServiceAccountSubjects(namespace string) []string {
var saSubjects []string
for _, s := range c.crb.Subjects {
if s.Kind == rbac.ServiceAccountKind && s.Namespace == namespace {
saSubjects = append(saSubjects, s.Name)
}
}
return saSubjects
}

type v1beta1ClusterRoleBinding struct {
crb rbacbeta.ClusterRoleBinding
}

func (c v1beta1ClusterRoleBinding) Name() string {
return c.crb.Name
}

func (c v1beta1ClusterRoleBinding) RoleRefName() string {
return c.crb.RoleRef.Name
}

func (c v1beta1ClusterRoleBinding) ServiceAccountSubjects(namespace string) []string {
var saSubjects []string
for _, s := range c.crb.Subjects {
if s.Kind == rbac.ServiceAccountKind && s.Namespace == namespace {
saSubjects = append(saSubjects, s.Name)
}
}
return saSubjects
}
37 changes: 25 additions & 12 deletions pkg/backup/service_account_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,41 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
rbacclient "k8s.io/client-go/kubernetes/typed/rbac/v1"

"github.com/heptio/ark/pkg/apis/ark/v1"
arkdiscovery "github.com/heptio/ark/pkg/discovery"
"github.com/heptio/ark/pkg/kuberesource"
)

// serviceAccountAction implements ItemAction.
type serviceAccountAction struct {
log logrus.FieldLogger
clusterRoleBindings []rbac.ClusterRoleBinding
clusterRoleBindings []ClusterRoleBinding
}

// NewServiceAccountAction creates a new ItemAction for service accounts.
func NewServiceAccountAction(log logrus.FieldLogger, client rbacclient.ClusterRoleBindingInterface) (ItemAction, error) {
clusterRoleBindings, err := client.List(metav1.ListOptions{})
func NewServiceAccountAction(log logrus.FieldLogger, clusterRoleBindingListers map[string]ClusterRoleBindingLister, discoveryHelper arkdiscovery.Helper) (ItemAction, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a unit test on this now that it doesn't depend a full clientset. You could fake/mock a couple of ClusterRoleBindingListers to return a specific list of CRBs each, put them in the clusterRoleBindingListers arg, then set up a fake discoveryHelper with the relevant API groups that correspond to what's in the map (easiest way to set up the fake discoveryHelper is probably by not using the constructor function, but just instantiating directly and setting the apiGroups to what you need). Then you can verify that the service account action's clusterRoleBindings match what the expected lister had.

// Look up the supported RBAC version
var supportedAPI metav1.GroupVersionForDiscovery
for _, ag := range discoveryHelper.APIGroups() {
if ag.Name == rbac.GroupName {
supportedAPI = ag.PreferredVersion
break
}
}

crbLister := clusterRoleBindingListers[supportedAPI.Version]

// This should be safe because the List call will return a 0-item slice
// if there's no matching API version.
crbs, err := crbLister.List()
if err != nil {
return nil, errors.WithStack(err)
return nil, err
}

return &serviceAccountAction{
log: log,
clusterRoleBindings: clusterRoleBindings.Items,
clusterRoleBindings: crbs,
}, nil
}

Expand Down Expand Up @@ -76,14 +89,14 @@ func (a *serviceAccountAction) Execute(item runtime.Unstructured, backup *v1.Bac
roles = sets.NewString()
)

for _, clusterRoleBinding := range a.clusterRoleBindings {
for _, subj := range clusterRoleBinding.Subjects {
if subj.Kind == rbac.ServiceAccountKind && subj.Namespace == namespace && subj.Name == name {
for _, crb := range a.clusterRoleBindings {
for _, s := range crb.ServiceAccountSubjects(namespace) {
if s == name {
a.log.Infof("Adding clusterrole %s and clusterrolebinding %s to additionalItems since serviceaccount %s/%s is a subject",
clusterRoleBinding.RoleRef.Name, clusterRoleBinding.Name, namespace, name)
crb.RoleRefName(), crb, namespace, name)

bindings.Insert(clusterRoleBinding.Name)
roles.Insert(clusterRoleBinding.RoleRef.Name)
bindings.Insert(crb.Name())
roles.Insert(crb.RoleRefName())
break
}
}
Expand Down
Loading