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

BUILD-725: Adds build & deployer controller #289

Merged
merged 1 commit into from
Mar 28, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
43 changes: 24 additions & 19 deletions pkg/authorization/defaultrolebindings/defaultrolebindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,46 @@ import (
"k8s.io/klog/v2"
)

var defaultRoleBindingNames = GetBootstrapServiceAccountProjectRoleBindingNames()

// DefaultRoleBindingController is a controller to combine cluster roles
type DefaultRoleBindingController struct {
// RoleBindingController is a controller to combine cluster roles
type RoleBindingController struct {
name string
roleBindingClient rbacclient.RoleBindingsGetter

roleBindingLister rbaclisters.RoleBindingLister
roleBindingSynced cache.InformerSynced
namespaceLister corelisters.NamespaceLister
namespaceSynced cache.InformerSynced

syncHandler func(namespace string) error
queue workqueue.RateLimitingInterface
syncHandler func(namespace string) error
queue workqueue.RateLimitingInterface
roleBindingsFunc projectRoleBindings
}

// NewDefaultRoleBinding creates a new controller
func NewDefaultRoleBindingsController(roleBindingInformer rbacinformers.RoleBindingInformer, namespaceInformer coreinformers.NamespaceInformer, roleBindingClient rbacclient.RoleBindingsGetter) *DefaultRoleBindingController {
c := &DefaultRoleBindingController{
// NewRoleBinding creates a new controller
func NewRoleBindingsController(roleBindingInformer rbacinformers.RoleBindingInformer, namespaceInformer coreinformers.NamespaceInformer, roleBindingClient rbacclient.RoleBindingsGetter, controllerName string) *RoleBindingController {
c := &RoleBindingController{
name: controllerName,
roleBindingClient: roleBindingClient,

roleBindingLister: roleBindingInformer.Lister(),
roleBindingSynced: roleBindingInformer.Informer().HasSynced,
namespaceLister: namespaceInformer.Lister(),
namespaceSynced: namespaceInformer.Informer().HasSynced,

queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "DefaultRoleBindingsController"),
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), controllerName),
}
c.syncHandler = c.syncNamespace
c.roleBindingsFunc = GetRoleBindingsForController(controllerName)

roleBindingNames := GetBootstrapServiceAccountProjectRoleBindingNames(c.roleBindingsFunc)

roleBindingInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
FilterFunc: func(obj interface{}) bool {
metadata, err := meta.Accessor(obj)
if err != nil {
return false
}
return defaultRoleBindingNames.Has(metadata.GetName())
return roleBindingNames.Has(metadata.GetName())
},
Handler: cache.ResourceEventHandlerFuncs{
DeleteFunc: func(uncast interface{}) {
Expand Down Expand Up @@ -94,7 +98,7 @@ func NewDefaultRoleBindingsController(roleBindingInformer rbacinformers.RoleBind
return c
}

func (c *DefaultRoleBindingController) syncNamespace(namespaceName string) error {
func (c *RoleBindingController) syncNamespace(namespaceName string) error {
namespace, err := c.namespaceLister.Get(namespaceName)
if errors.IsNotFound(err) {
return nil
Expand All @@ -112,7 +116,8 @@ func (c *DefaultRoleBindingController) syncNamespace(namespaceName string) error
}

errs := []error{}
desiredRoleBindings := GetBootstrapServiceAccountProjectRoleBindings(namespaceName)
desiredRoleBindings := GetRoleBindingsForController(c.name)(namespaceName)

for i := range desiredRoleBindings {
desiredRoleBinding := desiredRoleBindings[i]
found := false
Expand Down Expand Up @@ -142,14 +147,14 @@ func (c *DefaultRoleBindingController) syncNamespace(namespaceName string) error
}

// Run starts the controller and blocks until stopCh is closed.
func (c *DefaultRoleBindingController) Run(workers int, stopCh <-chan struct{}) {
func (c *RoleBindingController) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer c.queue.ShutDown()

klog.Infof("Starting DefaultRoleBindingController")
defer klog.Infof("Shutting down DefaultRoleBindingController")
klog.Infof("Starting %v", c.name)
defer klog.Infof("Shutting down %v", c.name)

if !cache.WaitForNamedCacheSync("DefaultRoleBindingController", stopCh, c.roleBindingSynced, c.namespaceSynced) {
if !cache.WaitForNamedCacheSync(c.name, stopCh, c.roleBindingSynced, c.namespaceSynced) {
return
}

Expand All @@ -160,12 +165,12 @@ func (c *DefaultRoleBindingController) Run(workers int, stopCh <-chan struct{})
<-stopCh
}

func (c *DefaultRoleBindingController) runWorker() {
func (c *RoleBindingController) runWorker() {
for c.processNextWorkItem() {
}
}

func (c *DefaultRoleBindingController) processNextWorkItem() bool {
func (c *RoleBindingController) processNextWorkItem() bool {
dsKey, quit := c.queue.Get()
if quit {
return false
Expand Down
123 changes: 90 additions & 33 deletions pkg/authorization/defaultrolebindings/defaultrolebindings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,25 @@ import (
"k8s.io/kubernetes/pkg/controller"
)

var controllerNames = []string{
"DefaultRoleBindingController",
"BuilderRoleBindingController",
"ImagePullerRoleBindingController",
"DeployerRoleBindingController",
}

func TestSync(t *testing.T) {
tests := []struct {
name string
controller string
startingNamespaces []*corev1.Namespace
startingRoleBindings []*rbacv1.RoleBinding
namespaceToSync string
expectedRoleBindingsNames []string
}{
{
name: "create-all",
name: "create-default-all",
controller: "DefaultRoleBindingController",
startingNamespaces: []*corev1.Namespace{
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
},
Expand All @@ -36,25 +45,65 @@ func TestSync(t *testing.T) {
expectedRoleBindingsNames: []string{"system:image-pullers", "system:image-builders", "system:deployers"},
},
{
name: "create-missing",
name: "create-builder",
controller: "BuilderRoleBindingController",
startingNamespaces: []*corev1.Namespace{
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
},
startingRoleBindings: []*rbacv1.RoleBinding{
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "system:image-pullers"}},
},
namespaceToSync: "foo",
expectedRoleBindingsNames: []string{"system:image-builders"},
},
{
name: "create-deployer",
controller: "DeployerRoleBindingController",
startingNamespaces: []*corev1.Namespace{
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
},
startingRoleBindings: []*rbacv1.RoleBinding{
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "system:image-builders"}},
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "system:image-pullers"}},
},
namespaceToSync: "foo",
expectedRoleBindingsNames: []string{"system:deployers"},
},
{
name: "create-image-puller",
controller: "ImagePullerRoleBindingController",
startingNamespaces: []*corev1.Namespace{
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
},
startingRoleBindings: []*rbacv1.RoleBinding{
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}},
},
namespaceToSync: "foo",
expectedRoleBindingsNames: []string{"system:image-pullers"},
},
{
name: "create-default-missing",
controller: "DefaultRoleBindingController",
startingNamespaces: []*corev1.Namespace{
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
{ObjectMeta: metav1.ObjectMeta{Name: "new"}},
},
startingRoleBindings: []*rbacv1.RoleBinding{
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "system:image-builders"}},
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}},
},
namespaceToSync: "foo",
expectedRoleBindingsNames: []string{"system:image-pullers", "system:deployers"},
},
{
name: "create-none",
name: "create-default-none",
controller: "DefaultRoleBindingController",
startingNamespaces: []*corev1.Namespace{
{ObjectMeta: metav1.ObjectMeta{Name: "foo"}},
},
startingRoleBindings: []*rbacv1.RoleBinding{
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "system:image-builders"}},
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "system:image-pullers"}},
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "system:image-builders"}},
{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "system:deployers"}},
},
namespaceToSync: "foo",
Expand All @@ -75,42 +124,50 @@ func TestSync(t *testing.T) {
namespaceIndexer.Add(obj)
}
fakeClient := kubeclientfake.NewSimpleClientset(objs...)
c := DefaultRoleBindingController{
roleBindingClient: fakeClient.RbacV1(),
roleBindingLister: rbaclisters.NewRoleBindingLister(roleBindingIndexer),
namespaceLister: corelisters.NewNamespaceLister(namespaceIndexer),
}

err := c.syncNamespace(test.namespaceToSync)
if err != nil {
t.Fatal(err)
}
for _, cName := range controllerNames {
c := RoleBindingController{
name: cName,
roleBindingClient: fakeClient.RbacV1(),
roleBindingLister: rbaclisters.NewRoleBindingLister(roleBindingIndexer),
namespaceLister: corelisters.NewNamespaceLister(namespaceIndexer),
}

allActions := fakeClient.Actions()
createActions := []clienttesting.CreateAction{}
for i := range allActions {
action := allActions[i]
createAction, ok := action.(clienttesting.CreateAction)
if !ok {
t.Errorf("unexpected action %#v", action)
if c.name != test.controller {
continue
}
createActions = append(createActions, createAction)
}
if len(createActions) != len(test.expectedRoleBindingsNames) {
t.Fatalf("expected %v, got %#v", test.expectedRoleBindingsNames, createActions)
}

for i, name := range test.expectedRoleBindingsNames {
action := createActions[i]
metadata, err := meta.Accessor(action.GetObject())
err := c.syncNamespace(test.namespaceToSync)
if err != nil {
t.Fatal(err)
}
if name != metadata.GetName() {
t.Errorf("expected %v, got %v", name, metadata.GetName())

allActions := fakeClient.Actions()
createActions := []clienttesting.CreateAction{}
for i := range allActions {
action := allActions[i]
createAction, ok := action.(clienttesting.CreateAction)
if !ok {
t.Errorf("unexpected action %#v", action)
}
createActions = append(createActions, createAction)
}
if action.GetNamespace() != test.namespaceToSync {
t.Errorf("expected %v, got %v", test.namespaceToSync, action.GetNamespace())

if len(createActions) != len(test.expectedRoleBindingsNames) {
t.Fatalf("expected %v, got %#v", test.expectedRoleBindingsNames, createActions)
}

for i, name := range test.expectedRoleBindingsNames {
action := createActions[i]
metadata, err := meta.Accessor(action.GetObject())
if err != nil {
t.Fatal(err)
}
if name != metadata.GetName() {
t.Errorf("expected %v, got %v", name, metadata.GetName())
}
if action.GetNamespace() != test.namespaceToSync {
t.Errorf("expected %v, got %v", test.namespaceToSync, action.GetNamespace())
}
}
}
})
Expand Down
58 changes: 50 additions & 8 deletions pkg/authorization/defaultrolebindings/project_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,69 @@ const (
DeployerServiceAccountName = "deployer"
)

func GetBootstrapServiceAccountProjectRoleBindings(namespace string) []rbacv1.RoleBinding {
type projectRoleBindings func(namespace string) []rbacv1.RoleBinding
type serviceAccountRoleBinding func(namespace string) rbacv1.RoleBinding

// GetImagePullerProjectRoleBindings generates a role binding that allows all pods to pull ImageStream images associated with given namespace.
// These should only be created if the "ImageRegistry" capability is enabled on the cluster.
func GetImagePullerProjectRoleBindings(namespace string) rbacv1.RoleBinding {
imagePuller := newOriginRoleBindingForClusterRoleWithGroup(ImagePullerRoleBindingName, ImagePullerRoleName, namespace, serviceaccount.MakeNamespaceGroupName(namespace))
imagePuller.Annotations[openShiftDescription] = "Allows all pods in this namespace to pull images from this namespace. It is auto-managed by a controller; remove subjects to disable."

return imagePuller
}

// GetBuilderServiceAccountProjectRoleBindings generates the role bindings specific to the "builder" service account of given namespace.
// These should only be created if the "Build" capability is enabled on the cluster.
func GetBuilderServiceAccountProjectRoleBindings(namespace string) rbacv1.RoleBinding {
apoorvajagtap marked this conversation as resolved.
Show resolved Hide resolved
imageBuilder := newOriginRoleBindingForClusterRoleWithSA(ImageBuilderRoleBindingName, ImageBuilderRoleName, namespace, BuilderServiceAccountName)
imageBuilder.Annotations[openShiftDescription] = "Allows builds in this namespace to push images to this namespace. It is auto-managed by a controller; remove subjects to disable."

return imageBuilder
}

// GetDeployerServiceAccountProjectRoleBindings generates the role bindings specific to the "builder" service account of given namespace.
// These should only be created if the "DeploymentConfig" capability is enabled on the cluster.
func GetDeployerServiceAccountProjectRoleBindings(namespace string) rbacv1.RoleBinding {
apoorvajagtap marked this conversation as resolved.
Show resolved Hide resolved
deployer := newOriginRoleBindingForClusterRoleWithSA(DeployerRoleBindingName, DeployerRoleName, namespace, DeployerServiceAccountName)
deployer.Annotations[openShiftDescription] = "Allows deploymentconfigs in this namespace to rollout pods in this namespace. It is auto-managed by a controller; remove subjects to disable."

return []rbacv1.RoleBinding{
imagePuller,
imageBuilder,
deployer,
return deployer
}

func composeRoleBindings(roleBindings ...serviceAccountRoleBinding) projectRoleBindings {
return func(namespace string) []rbacv1.RoleBinding {
bindings := []rbacv1.RoleBinding{}
for _, rbfunc := range roleBindings {
bindings = append(bindings, rbfunc(namespace))
}
return bindings
}
}

// GetRoleBindingsForController returns the appropriate generator function for the
// given named controller that will reconcile role bindings in a namespace.
func GetRoleBindingsForController(controller string) projectRoleBindings {
apoorvajagtap marked this conversation as resolved.
Show resolved Hide resolved
switch controller {
case "BuilderRoleBindingController":
return composeRoleBindings(GetBuilderServiceAccountProjectRoleBindings)
case "DeployerRoleBindingController":
return composeRoleBindings(GetDeployerServiceAccountProjectRoleBindings)
case "ImagePullerRoleBindingController":
return composeRoleBindings(GetImagePullerProjectRoleBindings)
default:
return composeRoleBindings(GetImagePullerProjectRoleBindings,
GetBuilderServiceAccountProjectRoleBindings,
GetDeployerServiceAccountProjectRoleBindings,
)
}
}

func GetBootstrapServiceAccountProjectRoleBindingNames() sets.String {
names := sets.NewString()
func GetBootstrapServiceAccountProjectRoleBindingNames(roleBindings projectRoleBindings) sets.Set[string] {
names := sets.Set[string]{}

for _, roleBinding := range GetBootstrapServiceAccountProjectRoleBindings("default") {
// Gets the roleBinding Names in the "default" namespace
for _, roleBinding := range roleBindings("default") {
names.Insert(roleBinding.Name)
}

Expand Down