Skip to content

Commit

Permalink
Option to customize NamespaceTransfomer role binding subject handling
Browse files Browse the repository at this point in the history
  • Loading branch information
KnVerey committed Jul 15, 2022
1 parent ab09d27 commit 2091c22
Show file tree
Hide file tree
Showing 5 changed files with 343 additions and 33 deletions.
59 changes: 44 additions & 15 deletions api/filters/namespace/namespace.go
Expand Up @@ -22,9 +22,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{}

Expand All @@ -47,9 +63,9 @@ 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)
ns.FsSlice = ns.removeRoleBindingSubjectFieldSpecs(ns.FsSlice)
if err := ns.roleBindingHack(node, gvk); err != nil {
return nil, err
}
Expand Down Expand Up @@ -82,12 +98,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), 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
Expand All @@ -97,29 +117,36 @@ 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) {
if !isRoleBinding(gvk.Kind) || ns.SetRoleBindingSubjects == NoSubjects {
return nil
}

// 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.
// only consider mutating service accounts
name, err := o.Pipe(
yaml.Lookup("name"), yaml.Match("default"),
yaml.Lookup("kind"), yaml.Match("ServiceAccount"),
)
if err != nil || yaml.IsMissingOrNull(name) {
return err
}
if ns.SetRoleBindingSubjects == DefaultSubjectsOnly || ns.SetRoleBindingSubjects == SubjectModeUnspecified {
// Force the namespace for the "default" service account only.
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
// set the namespace
node, err := o.Pipe(
yaml.LookupCreate(yaml.ScalarNode, "namespace"),
)
Expand All @@ -137,12 +164,13 @@ func isRoleBinding(kind string) bool {
return kind == roleBindingKind || kind == clusterRoleBindingKind
}

// removeRoleBindingFieldSpecs removes from the list fieldspecs that
// 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])
Expand Down Expand Up @@ -170,6 +198,7 @@ func (ns *Filter) fieldSetter() filtersutil.SetFn {

const (
subjectsField = "subjects"
subjectsNamespacePath = "subjects/namespace"
roleBindingKind = "RoleBinding"
clusterRoleBindingKind = "ClusterRoleBinding"
)
29 changes: 22 additions & 7 deletions api/internal/builtins/NamespaceTransformer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions api/konfig/builtinpluginconsts/namespace.go
Expand Up @@ -6,14 +6,12 @@ package builtinpluginconsts
const (
namespaceFieldSpecs = `
namespace:
- path: metadata/namespace
create: true
- path: metadata/name
kind: Namespace
create: true
- path: subjects
- path: subjects/namespace
kind: RoleBinding
- path: subjects
- path: subjects/namespace
kind: ClusterRoleBinding
- path: spec/service/namespace
group: apiregistration.k8s.io
Expand Down
29 changes: 22 additions & 7 deletions plugin/builtin/namespacetransformer/NamespaceTransformer.go
Expand Up @@ -10,14 +10,16 @@ import (
"sigs.k8s.io/kustomize/api/filters/namespace"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/yaml"
)

// Change or set the namespace of non-cluster level resources.
type plugin struct {
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
UnsetOnly bool `json:"unsetOnly" yaml:"unsetOnly"`
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
UnsetOnly bool `json:"unsetOnly" yaml:"unsetOnly"`
SetRoleBindingSubjects namespace.RoleBindingSubjectMode `json:"setRoleBindingSubjects" yaml:"setRoleBindingSubjects"`
}

//noinspection GoUnusedGlobalVariable
Expand All @@ -27,7 +29,19 @@ func (p *plugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.Namespace = ""
p.FieldSpecs = nil
return yaml.Unmarshal(c, p)
if err := yaml.Unmarshal(c, p); err != nil {
return errors.WrapPrefixf(err, "unmarshalling NamespaceTransformer config")
}
switch p.SetRoleBindingSubjects {
case namespace.AllServiceAccountSubjects, namespace.DefaultSubjectsOnly, namespace.NoSubjects:
// valid
case namespace.SubjectModeUnspecified:
p.SetRoleBindingSubjects = namespace.DefaultSubjectsOnly
default:
return errors.Errorf("invalid value %s for setRoleBindingSubjects", p.SetRoleBindingSubjects)
}

return nil
}

func (p *plugin) Transform(m resmap.ResMap) error {
Expand All @@ -41,9 +55,10 @@ func (p *plugin) Transform(m resmap.ResMap) error {
}
r.StorePreviousId()
if err := r.ApplyFilter(namespace.Filter{
Namespace: p.Namespace,
FsSlice: p.FieldSpecs,
UnsetOnly: p.UnsetOnly,
Namespace: p.Namespace,
FsSlice: p.FieldSpecs,
SetRoleBindingSubjects: p.SetRoleBindingSubjects,
UnsetOnly: p.UnsetOnly,
}); err != nil {
return err
}
Expand Down

0 comments on commit 2091c22

Please sign in to comment.