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

Conversation

nrb
Copy link
Contributor

@nrb nrb commented Jul 18, 2018

Fixes #660
Signed-off-by: Nolan Brubaker [email protected]

"github.com/heptio/ark/pkg/kuberesource"
)

type clusterRoleBindingVersionWrapper struct {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would like some guidance here - I don't think this struct is the way to go, but I was struggling coming up with an interface, given the ClusterRoleBinding is just a struct w/o methods.

@nrb nrb force-pushed the support-rbac-1.7 branch 2 times, most recently from 9ce51eb to 0a66211 Compare July 18, 2018 20:24
@nrb nrb added the Bug label Jul 18, 2018
// Name returns the name of a ClusterRoleBinding.
Name() string
// Subjects translates a ClusterRoleBindings Subjects slice into a slice of subject wrapper objects.
Subjects() []subject
Copy link
Contributor

Choose a reason for hiding this comment

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

One thing you could do is instead of exposing Subjects() and needing the wrapper type for that, just add a method to the ClusterRoleBinding interface called HasSubject that takes a subject kind, namespace, and name, and returns true or false -- since that's all we really need. That would minimize the amount of this wrapper code that we need

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually may need to be HasServiceAccountSubject so it doesn't have to take the kind (since that's part of the API group/version), but same idea.

@nrb nrb force-pushed the support-rbac-1.7 branch from 0a66211 to fe2dd12 Compare July 19, 2018 20:43
@nrb nrb changed the title WIP support for rbac v1beta1 on kubernetes v1.7.5+ Support for rbac v1beta1 on kubernetes v1.7.5+ Jul 19, 2018
@nrb nrb requested a review from carlisia July 19, 2018 20:45
@nrb nrb added this to the v0.9.1 milestone Jul 19, 2018
Copy link
Contributor

@skriss skriss left a comment

Choose a reason for hiding this comment

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

Haven't gone through units in detail yet

rbacclientbeta "k8s.io/client-go/kubernetes/typed/rbac/v1beta1"
)

type CRBLister interface {
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 I'd prefer to spell out ClusterRoleBinding in the type names (interface + 4 structs). I'm fine using abbreviations for local variable names though

List() ([]ClusterRoleBinding, error)
}

type V1CRBLister struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

this and the V1beta1CRBLister1 don't need to be exported, just the interface

}

// NewCRBLister wraps the v1beta1 and v1 RBAC client's List with a common interface.
func NewCRBLister(log logrus.FieldLogger, clientset kubernetes.Interface) (CRBLister, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

spell out ClusterRoleBinding in the func name


// NewCRBLister wraps the v1beta1 and v1 RBAC client's List with a common interface.
func NewCRBLister(log logrus.FieldLogger, clientset kubernetes.Interface) (CRBLister, error) {
var lister CRBLister
Copy link
Contributor

Choose a reason for hiding this comment

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

usually declare local vars closer to their usage (unlike a bunch of other languages)

return nil, errors.WithStack(err)
}
var supportedAPI metav1.GroupVersionForDiscovery
apigroups := discoveryHelper.APIGroups()
Copy link
Contributor

Choose a reason for hiding this comment

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

no real need to declare apigroups - can just range over discoveryHelper.APIGroups() in the next line

var supportedAPI metav1.GroupVersionForDiscovery
apigroups := discoveryHelper.APIGroups()
for _, ag := range apigroups {
if ag.Name == "rbac.authorization.k8s.io" {
Copy link
Contributor

@skriss skriss Jul 19, 2018

Choose a reason for hiding this comment

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

s/"rbac.authorization.k8s.io"/rbac.GroupName


switch supportedAPI.Version {
case "v1":
lister = V1CRBLister{client: clientset.RbacV1().ClusterRoleBindings()}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd just directly return the lister, nil

case "v1":
lister = V1CRBLister{client: clientset.RbacV1().ClusterRoleBindings()}
case "v1beta1":
lister = V1beta1CRBLister{client: clientset.RbacV1beta1().ClusterRoleBindings()}
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd just directly return the lister, nil

case "v1beta1":
lister = V1beta1CRBLister{client: clientset.RbacV1beta1().ClusterRoleBindings()}
// Return an empty client if we don't have a known version.
default:
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd just directly return nil, nil for the default (or get rid of default and return it outside the switch)

@@ -1,5 +1,5 @@
/*
Copyright 2018 the Heptio Ark contributors.

Copy link
Contributor

Choose a reason for hiding this comment

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

undo deletion of copyright

}

switch supportedAPI.Version {
case "v1":
Copy link
Contributor

Choose a reason for hiding this comment

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

could use rbac.SchemeGroupVersion.Version rather than "v1" here, and rbacbeta.... for the v1beta1 case

Copy link
Contributor

@carlisia carlisia left a comment

Choose a reason for hiding this comment

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

I had a minor style comment only. There are some exported methods that don't have a description, some are obvious and others not so obvious to me, so my additional comment is to add moar description if it makes sense. Other than this, lgtm as far as I can tell.

}

func (v1beta1 V1beta1CRBLister) List() ([]ClusterRoleBinding, error) {
var crbs []ClusterRoleBinding
Copy link
Contributor

Choose a reason for hiding this comment

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

Wanted to extend on what @skriss said to make sure this and I think one additional case of this is also changed: I'd move this crbs var to right above the for loop where it's used. It's a common thing in Go so it's expected but also avoids scrolling all over for where it's used if we expect its first use to happen right after where it's declared.

@nrb
Copy link
Contributor Author

nrb commented Jul 20, 2018

There are some exported methods that don't have a description

Which ones in particular? Happy to add more comments, though I'm not sure what's missing. The ones exposed via interfaces should be commented on the interfaces themselves.

@nrb nrb force-pushed the support-rbac-1.7 branch from fe2dd12 to 42dc865 Compare July 20, 2018 02:04
@carlisia
Copy link
Contributor

The ones exposed via interfaces should be commented on the interfaces themselves.

My bad, @nrb, those were precisely the already commented methods of interfaces :) 👍

@nrb
Copy link
Contributor Author

nrb commented Jul 20, 2018

Tested this with Kubernetes v1.7.16. The server started (where it would previously fail), and the cluster role bindings we create with the Ark installation were backed up.

% tar xzvf arktest-data.tar.gz
resources/namespaces/cluster/heptio-ark.json
resources/secrets/namespaces/heptio-ark/ark-token-rd7w2.json
resources/secrets/namespaces/heptio-ark/cloud-credentials.json
resources/secrets/namespaces/heptio-ark/default-token-lr9x1.json
resources/clusterrolebindings.rbac.authorization.k8s.io/cluster/ark.json
resources/clusterroles.rbac.authorization.k8s.io/cluster/cluster-admin.json
resources/serviceaccounts/namespaces/heptio-ark/ark.json
resources/serviceaccounts/namespaces/heptio-ark/default.json
resources/configs.ark.heptio.com/namespaces/heptio-ark/default.json
resources/backups.ark.heptio.com/namespaces/heptio-ark/arktest.json
resources/backups.ark.heptio.com/namespaces/heptio-ark/nginx.json
% cat resources/clusterrolebindings.rbac.authorization.k8s.io/cluster/ark.json
{"apiVersion":"rbac.authorization.k8s.io/v1beta1","kind":"ClusterRoleBinding","metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"rbac.authorization.k8s.io/v1beta1\",\"kind\":\"ClusterRoleBinding\",\"metadata\":{\"annotations\":{},\"labels\":{\"component\":\"ark\"},\"name\":\"ark\",\"namespace\":\"\"},\"roleRef\":{\"apiGroup\":\"rbac.authorization.k8s.io\",\"kind\":\"ClusterRole\",\"name\":\"cluster-admin\"},\"subjects\":[{\"kind\":\"ServiceAccount\",\"name\":\"ark\",\"namespace\":\"heptio-ark\"}]}\n"},"creationTimestamp":"2018-07-20T16:30:02Z","labels":{"component":"ark"},"name":"ark","resourceVersion":"1348","selfLink":"/apis/rbac.authorization.k8s.io/v1beta1/clusterrolebindings/ark","uid":"2721ffd3-8c3a-11e8-9722-0a1f5e4b1da4"},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"ClusterRole","name":"cluster-admin"},"subjects":[{"kind":"ServiceAccount","name":"ark","namespace":"heptio-ark"}]}% 

}

func (v1beta1 v1beta1ClusterRoleBindingLister) List() ([]ClusterRoleBinding, error) {

Copy link
Contributor

Choose a reason for hiding this comment

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

rm blank line

func NewClusterRoleBindingLister(log logrus.FieldLogger, clientset kubernetes.Interface) (ClusterRoleBindingLister, error) {
discoveryHelper, err := arkdiscovery.NewHelper(clientset.Discovery(), log)
if err != nil {
return nil, errors.WithStack(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

don't need a errors.WithStack here since the error is coming from ark code so should already have a stack

}

// v1 implementation
type v1crb struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

i'd probably spell out ClusterRoleBinding in the type name here


// v1beta1 implementation

type v1beta1crb struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

i'd probably spell out ClusterRoleBinding in the type name here

return saSubjects
}

// v1beta1 implementation
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd remove this, put a godoc comment on the struct if you want but not necessary

roles.Insert(clusterRoleBinding.RoleRef.Name)
break
bindings.Insert(crb.Name())
roles.Insert(crb.RoleRefName())
Copy link
Contributor

Choose a reason for hiding this comment

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

you can still break once you've found a matching subject

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 it not valid to have multiple service account subjects? Wouldn't we want to back them all up?

Copy link
Contributor

@skriss skriss Jul 20, 2018

Choose a reason for hiding this comment

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

We are trying to back up CRBs/CRs that have the current service account item as a subject. Once we know that a given CRB does have this SA as a subject, we're going to back it up. No need to look at all the other service accounts that this CRB applies to at that point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added back

@@ -45,6 +45,8 @@ type Helper interface {
// Refresh pulls an updated set of Ark-backuppable resources from the
// discovery API.
Refresh() error

APIGroups() []metav1.APIGroup
Copy link
Contributor

Choose a reason for hiding this comment

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

add godoc

@nrb
Copy link
Contributor Author

nrb commented Jul 20, 2018

Double-checking with a 1.9 cluster on GKE, ClusterRoleBindings still back up as expected with this change.

@nrb nrb force-pushed the support-rbac-1.7 branch from 42dc865 to 99264c5 Compare July 20, 2018 18:51
cmd.CheckError(err)

if crbLister != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if crbLister is null (i.e. RBAC is not enabled on this cluster)? We still go through the code paths of setting up an action item plugin and serving it - seems like there may be a problem there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch - I had intended to address this and forgot.

Would it make sense to check if the action is nil before adding to the plugin configuration? Or maybe move the NewClusterRoleBindingLister logic into the NewServiceAccount action function?

Also, do we support clusters without RBAC? I didn't think we did, but even if that's the case this behavior needs to be fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know if we've ever really tried on clusters without RBAC. Hopefully shouldn't be much of a use case going fwd, but who knows.

I'd say it makes sense to always have a non-nil action. We can move the NewClusterRoleBindingLister into the NewServiceAccount func. If no API group is found, just leave the item action's clusterRoleBindings field as nil, and return the action/no error. Then Execute() becomes a no-op (more or less) in that scenario.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry I didn't notice this earlier

Copy link
Contributor Author

@nrb nrb Jul 20, 2018

Choose a reason for hiding this comment

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

np, better now than after tagging!

That logic makes sense. I'll have to modify the signature and the testing fakes a bit, but shouldn't be too bad.

@skriss
Copy link
Contributor

skriss commented Jul 20, 2018

Apart from the question re: plugin serving, the rest LGTM.

@nrb nrb force-pushed the support-rbac-1.7 branch 4 times, most recently from dbf87d2 to 6b2f56f Compare July 20, 2018 21:46
}

// NewClusterRoleBindingLister wraps the v1beta1 and v1 RBAC client's List with a common interface.
func NewClusterRoleBindingLister(log logrus.FieldLogger, clientset kubernetes.Interface, discoveryHelper arkdiscovery.Helper) (ClusterRoleBindingLister, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We could change the signature here so instead of taking a clientset, it takes a map[string]ListerConstructor, where the keys are RBAC API versions, and the values are functions that return a ClusterRoleBindingLister. Then, instead of having a switch statement on concrete versions, we just do a lookup into the map for the server's preferred version.

This logic would be easily testable without actually requiring a kubernetes.Interface, and the correctness of the map (which would be defined in plugin.go could be determined by inspection.

Food for thought

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like that idea - are you thinking of pairing that with a no-op lister then?

Copy link
Contributor

@skriss skriss Jul 20, 2018

Choose a reason for hiding this comment

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

Yeah, I think that'd be the way to go. So NewServiceAccountAction would go back to taking a logger and a CRBLister, NewClusterRoleBindingLister would take a logger, a discovery helper, and that map of constructor funcs, and return a no-op lister if there was no preferred version or no constructor for the preferred version, and then plugin.go would call NewClusterRoleBindingLister then pass the result to NewServiceAccountAction.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sketching this out, it might look something like

// In rbac.go
type ListerConstructor func() ClusterRoleBindingLister
NewV1clusterrolebindinglister()
NewV1beta1clusterrolebindinglister()
NewRBACDisabledLister

// In plugins.go
listerFuncs := map[string]ListerConstructor {
rbac.SchemeGroupVersion.Version: NewV1clusterrolebindinglister,
rbacbeta.SchemeGroupVersion.Version: NewV1betaclusterrolebindinglister,
"": NewRBACDisabledLister // returns an empty list
}

action, err := NewServiceAccountAction(log, discoveryHelper, listerFuncs)

// In service_item_action.go/NewServiceAccountAction
// apigroup logic moved from NewClusterRoleBindingLister

crbLister := listerFuncs[apigroup.Version]()
crbs, err := crbLister.List()
if err != nil {
 return nil, err
}
return &serviceAccountAction{
log: log,
clusterRoleBindings: crbs,

I think it works; the listerFuncs map may also be specified in rbac.go and exported, looking at it here. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

yep, looks good!

@nrb nrb force-pushed the support-rbac-1.7 branch from 6b2f56f to 7fdb079 Compare July 20, 2018 23:54
}

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

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

}

// NewV1beta1ClusterRoleBindingLister returns a ClusterRoleBindingLister for RBAC v1beta1.
func NewV1beta1ClusterRoleBindingLister() ClusterRoleBindingLister {
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be removed

}

// NewV1ClusterRoleBindingLister returns a ClusterRoleBindingLister for RBAC v1.
func NewV1ClusterRoleBindingLister() ClusterRoleBindingLister {
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be removed

@nrb nrb force-pushed the support-rbac-1.7 branch from 7fdb079 to 3b9b762 Compare July 23, 2018 17:59
Copy link
Contributor

@skriss skriss left a comment

Choose a reason for hiding this comment

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

Looking good, just one unused type def to remove.

}

// ListerConstructor specifies a function which can return a ClusterRoleBindingLister.
type ListerConstructor func() ClusterRoleBindingLister
Copy link
Contributor

Choose a reason for hiding this comment

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

can remove this since it's not being used

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

Signed-off-by: Nolan Brubaker <[email protected]>
@nrb nrb force-pushed the support-rbac-1.7 branch from 3b9b762 to dd1e150 Compare July 23, 2018 18:19
Copy link
Contributor

@skriss skriss left a comment

Choose a reason for hiding this comment

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

LGTM.

@skriss
Copy link
Contributor

skriss commented Jul 23, 2018

@nrb all set to merge?

@nrb
Copy link
Contributor Author

nrb commented Jul 23, 2018

Yep, ready to merge from my POV. @carlisia do you want to do another pass?

@skriss
Copy link
Contributor

skriss commented Jul 23, 2018

I'm gonna go ahead and merge so we can get the patch release out. Can always follow up with additional PRs if necessary,

@skriss skriss merged commit e063b79 into vmware-tanzu:master Jul 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants