Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: Massimiliano Giovagnoli <[email protected]>
  • Loading branch information
maxgio92 committed Aug 15, 2022
1 parent 5a9c25b commit aaa126c
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 5 deletions.
13 changes: 11 additions & 2 deletions api/v1beta1/owner_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,30 @@ func (in OwnerSpec) GetRoles(tenant Tenant, index int) []string {

roles := []string{"admin", "capsule-namespace-deleter"}

if tenant.Spec.GitOpsReady {
roles = append(roles, in.getGitOpsRoles(tenant)...)
}

return roles
}

func (in OwnerSpec) GetClusterRoles(tenant Tenant) []string {
if tenant.Spec.GitOpsReady {
return in.getGitOpsRoles(tenant)
return in.getGitOpsClusterRoles(tenant)
}

return []string{}
}

func (in OwnerSpec) getGitOpsClusterRoles(tenant Tenant) []string {
return []string{
"capsule-tenant-impersonator-" + tenant.Name + "-" + in.Name,
}
}

func (in OwnerSpec) getGitOpsRoles(tenant Tenant) []string {
return []string{
"cluster-admin",
"capsule-tenant-impersonator-" + tenant.Name + "-" + in.Name,
}
}

Expand Down
2 changes: 2 additions & 0 deletions api/v1beta1/tenant_labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func GetTypeLabel(t runtime.Object) (label string, err error) {
return "capsule.clastix.io/resource-quota", nil
case *rbacv1.RoleBinding:
return "capsule.clastix.io/role-binding", nil
case *rbacv1.ClusterRoleBinding:
return "capsule.clastix.io/cluster-role-binding", nil
default:
err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v)
}
Expand Down
3 changes: 3 additions & 0 deletions config/crd/bases/capsule.clastix.io_tenants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,9 @@ spec:
allowedRegex:
type: string
type: object
gitOpsReady:
description: Configured RBAC for machine owners tailored for GitOps controllers.
type: boolean
imagePullPolicies:
description: Specify the allowed values for the imagePullPolicies option in Pod resources. Capsule assures that all Pod resources created in the Tenant can use only one of the allowed policy. Optional.
items:
Expand Down
119 changes: 119 additions & 0 deletions controllers/tenant/clusterrolebindings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package tenant

import (
"context"
"fmt"
"hash/fnv"

"golang.org/x/sync/errgroup"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)

// Sync the dynamic Tenant Owner specific cluster-roles and additional ClusterRole Bindings, which can be used in many ways:
// applying Pod Security Policies or giving access to CRDs or specific API groups.
func (r *Manager) syncClusterRoleBindings(ctx context.Context, tenant *capsulev1beta1.Tenant) (err error) {
// hashing the ClusterRoleBinding name due to DNS RFC-1123 applied to Kubernetes labels
hashFn := func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string {
h := fnv.New64a()

_, _ = h.Write([]byte(binding.ClusterRoleName))

for _, sub := range binding.Subjects {
_, _ = h.Write([]byte(sub.Kind + sub.Name))
}

return fmt.Sprintf("%x", h.Sum64())
}
// getting requested Role Binding keys
keys := make([]string, 0, len(tenant.Spec.Owners))
// Generating for dynamic tenant owners cluster roles
for _, owner := range tenant.Spec.Owners {
for _, clusterRoleName := range owner.GetClusterRoles(*tenant) {

cr := r.ownerClusterRoleBindings(owner, clusterRoleName)

keys = append(keys, hashFn(cr))
}
}

group := new(errgroup.Group)

group.Go(func() error {
return r.syncAdditionalClusterRoleBinding(ctx, tenant, keys, hashFn)
})

return group.Wait()
}

func (r *Manager) syncAdditionalClusterRoleBinding(ctx context.Context, tenant *capsulev1beta1.Tenant, keys []string, hashFn func(binding capsulev1beta1.AdditionalRoleBindingsSpec) string) (err error) {

var tenantLabel string

var clusterRoleBindingLabel string

if tenantLabel, err = capsulev1beta1.GetTypeLabel(&capsulev1beta1.Tenant{}); err != nil {
return
}

if clusterRoleBindingLabel, err = capsulev1beta1.GetTypeLabel(&rbacv1.ClusterRoleBinding{}); err != nil {
return
}

if err = r.pruningClusterResources(ctx, keys, &rbacv1.ClusterRoleBinding{}); err != nil {
return
}

var clusterRoleBindings []capsulev1beta1.AdditionalRoleBindingsSpec

for _, owner := range tenant.Spec.Owners {
for _, clusterRoleName := range owner.GetClusterRoles(*tenant) {
clusterRoleBindings = append(clusterRoleBindings, r.ownerClusterRoleBindings(owner, clusterRoleName))
}
}

for i, clusterRoleBinding := range clusterRoleBindings {

clusterRoleBindingHashLabel := hashFn(clusterRoleBinding)

target := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("capsule-%s-%d-%s", tenant.Name, i, clusterRoleBinding.ClusterRoleName),
},
}

var res controllerutil.OperationResult
res, err = controllerutil.CreateOrUpdate(ctx, r.Client, target, func() error {
target.ObjectMeta.Labels = map[string]string{
tenantLabel: tenant.Name,
clusterRoleBindingLabel: clusterRoleBindingHashLabel,
}
target.RoleRef = rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: clusterRoleBinding.ClusterRoleName,
}
target.Subjects = clusterRoleBinding.Subjects

return controllerutil.SetControllerReference(tenant, target, r.Client.Scheme())
})

// TODO: find appropriate event Namespace.
r.emitEvent(tenant, target.GetNamespace(), res, fmt.Sprintf("Ensuring ClusterRoleBinding %s", target.GetName()), err)

if err != nil {
r.Log.Error(err, "Cannot sync ClusterRoleBinding")
}

r.Log.Info(fmt.Sprintf("ClusterRoleBinding sync result: %s", string(res)), "name", target.Name, "namespace", target.Namespace)

if err != nil {
return
}
}

return nil
}
3 changes: 0 additions & 3 deletions controllers/tenant/clusterroles.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ func (r *Manager) ensureOwnerClusterRole(ctx context.Context, tenant *capsulev1b
}

resourceName := owner.Name
if owner.Kind == capsulev1beta1.ServiceAccountOwner {
resourceName = owner.Name
}

_, err = controllerutil.CreateOrUpdate(ctx, r.Client, clusterRole, func() error {
clusterRole.Rules = []rbacv1.PolicyRule{
Expand Down
8 changes: 8 additions & 0 deletions controllers/tenant/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct

return
}
// Ensuring ClusterRoleBindings resources
r.Log.Info("Ensuring ClusterRoleBindings for Owners and Tenant")

if err = r.syncClusterRoleBindings(ctx, instance); err != nil {
r.Log.Error(err, "Cannot sync ClusterRoleBindings items")

return
}
// Ensuring RoleBinding resources
r.Log.Info("Ensuring RoleBindings for Owners and Tenant")

Expand Down
41 changes: 41 additions & 0 deletions controllers/tenant/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,47 @@ import (
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)

// pruningClusterResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or
// NetworkPolicy using the "exists" and "notin" LabelSelector to perform an outer-join removal.
func (r *Manager) pruningClusterResources(ctx context.Context, keys []string, obj client.Object) (err error) {
var capsuleLabel string

if capsuleLabel, err = capsulev1beta1.GetTypeLabel(obj); err != nil {
return
}

selector := labels.NewSelector()

var exists *labels.Requirement

if exists, err = labels.NewRequirement(capsuleLabel, selection.Exists, []string{}); err != nil {
return
}

selector = selector.Add(*exists)

if len(keys) > 0 {
var notIn *labels.Requirement

if notIn, err = labels.NewRequirement(capsuleLabel, selection.NotIn, keys); err != nil {
return err
}

selector = selector.Add(*notIn)
}

r.Log.Info("Pruning objects with label selector " + selector.String())

return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
return r.DeleteAllOf(ctx, obj, &client.DeleteAllOfOptions{
ListOptions: client.ListOptions{
LabelSelector: selector,
},
DeleteOptions: client.DeleteOptions{},
})
})
}

// pruningResources is taking care of removing the no more requested sub-resources as LimitRange, ResourceQuota or
// NetworkPolicy using the "exists" and "notin" LabelSelector to perform an outer-join removal.
func (r *Manager) pruningResources(ctx context.Context, ns string, keys []string, obj client.Object) (err error) {
Expand Down

0 comments on commit aaa126c

Please sign in to comment.