Option to customize NamespaceTransfomer role binding subject handling

This commit is contained in:
Katrina Verey
2022-07-08 14:18:25 -04:00
parent ab09d27ec7
commit 0c37ee89af
5 changed files with 400 additions and 57 deletions

View File

@@ -7,6 +7,7 @@ import (
"sigs.k8s.io/kustomize/api/filters/filtersutil"
"sigs.k8s.io/kustomize/api/filters/fsslice"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/resid"
"sigs.k8s.io/kustomize/kyaml/yaml"
@@ -22,9 +23,25 @@ type Filter struct {
// UnsetOnly means only blank namespace fields will be set
UnsetOnly bool `json:"unsetOnly" yaml:"unsetOnly"`
// SetRoleBindingSubjects determines which subject fields in RoleBinding and ClusterRoleBinding
// objects will have their namespace fields set. Overrides field specs provided for these types, if any.
// - defaultOnly (default): namespace will be set only on subjects named "default".
// - allServiceAccounts: namespace will be set on all subjects with "kind: ServiceAccount"
// - none: all subjects will be skipped.
SetRoleBindingSubjects RoleBindingSubjectMode `json:"setRoleBindingSubjects" yaml:"setRoleBindingSubjects"`
trackableSetter filtersutil.TrackableSetter
}
type RoleBindingSubjectMode string
const (
DefaultSubjectsOnly RoleBindingSubjectMode = "defaultOnly"
SubjectModeUnspecified RoleBindingSubjectMode = ""
AllServiceAccountSubjects RoleBindingSubjectMode = "allServiceAccounts"
NoSubjects RoleBindingSubjectMode = "none"
)
var _ kio.Filter = Filter{}
var _ kio.TrackableFilter = &Filter{}
@@ -47,10 +64,10 @@ func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
return nil, err
}
// Special handling for (cluster) role binding -- :(
// Special handling for (cluster) role binding subjects -- :(
if isRoleBinding(gvk.Kind) {
ns.FsSlice = ns.removeRoleBindingFieldSpecs(ns.FsSlice)
if err := ns.roleBindingHack(node, gvk); err != nil {
ns.FsSlice = ns.removeRoleBindingSubjectFieldSpecs(ns.FsSlice)
if err := ns.roleBindingHack(node); err != nil {
return nil, err
}
}
@@ -82,12 +99,16 @@ func (ns Filter) metaNamespaceHack(obj *yaml.RNode, gvk resid.Gvk) error {
return err
}
// roleBindingHack is a hack for implementing the transformer's DefaultSubjectsOnly mode
// roleBindingHack is a hack for implementing the transformer's SetRoleBindingSubjects option
// for RoleBinding and ClusterRoleBinding resource types.
// In this mode, RoleBinding and ClusterRoleBinding have namespace set on
//
// In NoSubjects mode, it does nothing.
//
// In AllServiceAccountSubjects mode, it sets the namespace on subjects with "kind: ServiceAccount".
//
// In DefaultSubjectsOnly mode (default mode), RoleBinding and ClusterRoleBinding have namespace set on
// elements of the "subjects" field if and only if the subject elements
// "name" is "default". Otherwise the namespace is not set.
//
// Example:
//
// kind: RoleBinding
@@ -96,53 +117,65 @@ func (ns Filter) metaNamespaceHack(obj *yaml.RNode, gvk resid.Gvk) error {
// ...
// - name: "something-else" # this will not have the namespace set
// ...
func (ns Filter) roleBindingHack(obj *yaml.RNode, gvk resid.Gvk) error {
if !isRoleBinding(gvk.Kind) {
func (ns Filter) roleBindingHack(obj *yaml.RNode) error {
var visitor filtersutil.SetFn
switch ns.SetRoleBindingSubjects {
case NoSubjects:
return nil
case DefaultSubjectsOnly, SubjectModeUnspecified:
visitor = ns.setSubjectsNamedDefault
case AllServiceAccountSubjects:
visitor = ns.setServiceAccountNamespaces
default:
return errors.Errorf("invalid value %q for setRoleBindingSubjects: "+
"must be one of %q, %q or %q", ns.SetRoleBindingSubjects,
DefaultSubjectsOnly, NoSubjects, AllServiceAccountSubjects)
}
// Lookup the namespace field on all elements.
// Lookup the subjects field on all elements.
obj, err := obj.Pipe(yaml.Lookup(subjectsField))
if err != nil || yaml.IsMissingOrNull(obj) {
return err
}
// add the namespace to each "subject" with name: default
err = obj.VisitElements(func(o *yaml.RNode) error {
// The only case we need to force the namespace
// if for the "service account". "default" is
// kind of hardcoded here for right now.
name, err := o.Pipe(
yaml.Lookup("name"), yaml.Match("default"),
)
if err != nil || yaml.IsMissingOrNull(name) {
return err
}
// set the namespace for the default account
node, err := o.Pipe(
yaml.LookupCreate(yaml.ScalarNode, "namespace"),
)
if err != nil {
return err
}
return ns.fieldSetter()(node)
})
return err
// Use the appropriate visitor to set the namespace field on the correct subset of subjects
return errors.WrapPrefixf(obj.VisitElements(visitor), "setting namespace on (cluster)role binding subjects")
}
func isRoleBinding(kind string) bool {
return kind == roleBindingKind || kind == clusterRoleBindingKind
}
// removeRoleBindingFieldSpecs removes from the list fieldspecs that
func (ns Filter) setServiceAccountNamespaces(o *yaml.RNode) error {
name, err := o.Pipe(yaml.Lookup("kind"), yaml.Match("ServiceAccount"))
if err != nil || yaml.IsMissingOrNull(name) {
return errors.WrapPrefixf(err, "looking up kind on (cluster)role binding subject")
}
return setNamespaceField(o, ns.fieldSetter())
}
func (ns Filter) setSubjectsNamedDefault(o *yaml.RNode) error {
name, err := o.Pipe(yaml.Lookup("name"), yaml.Match("default"))
if err != nil || yaml.IsMissingOrNull(name) {
return errors.WrapPrefixf(err, "looking up name on (cluster)role binding subject")
}
return setNamespaceField(o, ns.fieldSetter())
}
func setNamespaceField(node *yaml.RNode, setter filtersutil.SetFn) error {
node, err := node.Pipe(yaml.LookupCreate(yaml.ScalarNode, "namespace"))
if err != nil {
return errors.WrapPrefixf(err, "setting namespace field on (cluster)role binding subject")
}
return setter(node)
}
// removeRoleBindingSubjectFieldSpecs removes from the list fieldspecs that
// have hardcoded implementations
func (ns Filter) removeRoleBindingFieldSpecs(fs types.FsSlice) types.FsSlice {
func (ns Filter) removeRoleBindingSubjectFieldSpecs(fs types.FsSlice) types.FsSlice {
var val types.FsSlice
for i := range fs {
if isRoleBinding(fs[i].Kind) && fs[i].Path == subjectsField {
if isRoleBinding(fs[i].Kind) &&
(fs[i].Path == subjectsNamespacePath || fs[i].Path == subjectsField) {
continue
}
val = append(val, fs[i])
@@ -170,6 +203,7 @@ func (ns *Filter) fieldSetter() filtersutil.SetFn {
const (
subjectsField = "subjects"
subjectsNamespacePath = "subjects/namespace"
roleBindingKind = "RoleBinding"
clusterRoleBindingKind = "ClusterRoleBinding"
)