Skip to content

Commit

Permalink
refactor(organizations): consolidation of organization controllers (#744
Browse files Browse the repository at this point in the history
)

* refac(organizations) Merge RBAC controller to Organization controller (#681)

* refac(organizations) Merge ServiceProxy controller to organization controller (#681)

* refac(organizations) Merge teamrole seeder to organization controller (#681)

* refac(organizations) Merge dex controller into organization controller (#681)

* refac(organizations) Rebase side effects (#681)

* refac(organizations) Remove unnecessary part of code (#681)

* refac(organizations) Remove duplicated part and add missing own from RBAC Controller (#681)

* refac(organizations) Add dexapi CRDs to organization (#681)

* refac(organizations) Rename files (#681)

* refac(organizations) Add statuses for steps of organization reconciliation (#681)

* Automatic generation of CRD API Docs

* Automatic generation of CRD API Docs

* refac(organizations) Make comments correct (#681)

* Automatic generation of CRD API Docs

* refac(organizations) Rename condition type to plural version (#681)

Co-authored-by: IvoGoman <[email protected]>

* Automatic generation of CRD API Docs

* refac(organizations) Fix renaming side effects (#681)

* Automatic generation of CRD API Docs

* Automatic generation of CRD API Docs

---------

Co-authored-by: Cloud Operator <[email protected]>
Co-authored-by: Abhijith Ravindra <[email protected]>
Co-authored-by: IvoGoman <[email protected]>
  • Loading branch information
4 people authored Nov 25, 2024
1 parent 7cf9cf7 commit 4431ae1
Show file tree
Hide file tree
Showing 15 changed files with 174 additions and 306 deletions.
10 changes: 3 additions & 7 deletions cmd/greenhouse/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ import (
// knownControllers contains all controllers to be registered when starting the operator.
var knownControllers = map[string]func(controllerName string, mgr ctrl.Manager) error{
// Organization controllers.
"organizationController": (&organizationcontrollers.OrganizationReconciler{}).SetupWithManager,
"organizationRBAC": (&organizationcontrollers.RBACReconciler{}).SetupWithManager,
"organizationDEX": startOrganizationDexReconciler,
"organizationServiceProxy": (&organizationcontrollers.ServiceProxyReconciler{}).SetupWithManager,
"organizationTeamRoleSeeder": (&organizationcontrollers.TeamRoleSeederReconciler{}).SetupWithManager,
"organizationController": startOrganizationReconciler,

// Team controllers.
"teamPropagation": (&teamcontrollers.TeamPropagationReconciler{}).SetupWithManager,
Expand Down Expand Up @@ -75,12 +71,12 @@ func isControllerEnabled(controllerName string) bool {
return false
}

func startOrganizationDexReconciler(name string, mgr ctrl.Manager) error {
func startOrganizationReconciler(name string, mgr ctrl.Manager) error {
namespace := "greenhouse"
if v, ok := os.LookupEnv("POD_NAMESPACE"); ok {
namespace = v
}
return (&organizationcontrollers.DexReconciler{
return (&organizationcontrollers.OrganizationReconciler{
Namespace: namespace,
}).SetupWithManager(name, mgr)
}
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/api/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.0.0
info:
title: Greenhouse
version: ca963d1
version: 388f129
description: PlusOne operations platform
paths:
/TeamMembership:
Expand Down
17 changes: 17 additions & 0 deletions pkg/apis/greenhouse/v1alpha1/organization_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,23 @@ const (
SCIMRequestFailedReason ConditionReason = "SCIMRequestFailed"
// SCIMConfigNotProvidedReason is set when scim config is not present in spec as it is optional
SCIMConfigNotProvidedReason ConditionReason = "SCIMConfigNotProvided"

// NamespaceCreated is set when the namespace for organization is created.
NamespaceCreated ConditionType = "NamespaceCreated"
// OrganizationRBACConfigured is set when the RBAC for organization is configured
OrganizationRBACConfigured ConditionType = "OrganizationRBACConfigured"
// OrganizationDefaultTeamRolesConfigured is set when default team roles are configured
OrganizationDefaultTeamRolesConfigured ConditionType = "OrganizationDefaultTeamRolesConfigured"
// ServiceProxyProvisioned is set when the service proxy is provisioned
ServiceProxyProvisioned ConditionType = "ServiceProxyProvisioned"
// OrganizationOICDConfigured is set when the OICD is configured
OrganizationOICDConfigured ConditionType = "OrganizationOICDConfigured"
// DexReconcileFailed is set when dex reconcile step has failed
DexReconcileFailed ConditionReason = "DexReconcileFailed"
// OAuthOICDFailed is set when OAuth reconciler has failed
OAuthOICDFailed ConditionReason = "OAuthOICDFailed"
// OrganizationAdminTeamConfigured is set when the admin team is configured for organization
OrganizationAdminTeamConfigured ConditionType = "OrganizationAdminTeamConfigured"
)

// OrganizationSpec defines the desired state of Organization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,20 @@ import (
corev1 "k8s.io/api/core/v1"
networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"

greenhousesapv1alpha1 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1"
"github.com/cloudoperators/greenhouse/pkg/clientutil"
"github.com/cloudoperators/greenhouse/pkg/common"
dexapi "github.com/cloudoperators/greenhouse/pkg/dex/api"
"github.com/cloudoperators/greenhouse/pkg/lifecycle"
"github.com/cloudoperators/greenhouse/pkg/util"
)

const dexConnectorTypeGreenhouse = "greenhouse-oidc"

// DexReconciler reconciles a Organization object
type DexReconciler struct {
client.Client
recorder record.EventRecorder
Namespace string
}

//+kubebuilder:rbac:groups=greenhouse.sap,resources=organizations,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=greenhouse.sap,resources=organizations/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=greenhouse.sap,resources=organizations/finalizers,verbs=update
Expand All @@ -51,52 +40,7 @@ type DexReconciler struct {
//+kubebuilder:rbac:groups=dex.coreos.com,resources=connectors;oauth2clients,verbs=get;list;watch;create;update;patch
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch

// SetupWithManager sets up the controller with the Manager.
func (r *DexReconciler) SetupWithManager(name string, mgr ctrl.Manager) error {
r.Client = mgr.GetClient()
r.recorder = mgr.GetEventRecorderFor(name)
if r.Namespace == "" {
return errors.New("namespace required but missing")
}
return ctrl.NewControllerManagedBy(mgr).
Named(name).
For(&greenhousesapv1alpha1.Organization{},
builder.WithPredicates(clientutil.PredicateHasOICDConfigured())).
Owns(&dexapi.Connector{}).
Owns(&dexapi.OAuth2Client{}).
// Watch secrets referenced by organizations for confidential values.
Watches(&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(r.enqueueOrganizationForReferencedSecret),
builder.WithPredicates(clientutil.PredicateHasOICDConfigured())).
Complete(r)
}

func (r *DexReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
return lifecycle.Reconcile(ctx, r.Client, req.NamespacedName, &greenhousesapv1alpha1.Organization{}, r, noStatus())
}

func (r *DexReconciler) EnsureDeleted(_ context.Context, _ lifecycle.RuntimeObject) (ctrl.Result, lifecycle.ReconcileResult, error) {
return ctrl.Result{}, lifecycle.Success, nil // nothing to do in that case
}

func (r *DexReconciler) EnsureCreated(ctx context.Context, object lifecycle.RuntimeObject) (ctrl.Result, lifecycle.ReconcileResult, error) {
org, ok := object.(*greenhousesapv1alpha1.Organization)
if !ok {
return ctrl.Result{}, lifecycle.Failed, errors.Errorf("RuntimeObject has incompatible type.")
}

if err := r.reconcileDexConnector(ctx, org); err != nil {
return ctrl.Result{}, lifecycle.Failed, err
}

if err := r.reconcileOAuth2Client(ctx, org); err != nil {
return ctrl.Result{}, lifecycle.Failed, err
}

return ctrl.Result{}, lifecycle.Success, nil
}

func (r *DexReconciler) reconcileDexConnector(ctx context.Context, org *greenhousesapv1alpha1.Organization) error {
func (r *OrganizationReconciler) reconcileDexConnector(ctx context.Context, org *greenhousesapv1alpha1.Organization) error {
clientID, err := clientutil.GetSecretKeyFromSecretKeyReference(ctx, r.Client, org.Name, org.Spec.Authentication.OIDCConfig.ClientIDReference)
if err != nil {
return err
Expand Down Expand Up @@ -147,15 +91,15 @@ func (r *DexReconciler) reconcileDexConnector(ctx context.Context, org *greenhou
return nil
}

func (r *DexReconciler) enqueueOrganizationForReferencedSecret(_ context.Context, o client.Object) []ctrl.Request {
func (r *OrganizationReconciler) enqueueOrganizationForReferencedSecret(_ context.Context, o client.Object) []ctrl.Request {
var org = new(greenhousesapv1alpha1.Organization)
if err := r.Get(context.Background(), types.NamespacedName{Namespace: "", Name: o.GetNamespace()}, org); err != nil {
return nil
}
return []ctrl.Request{{NamespacedName: client.ObjectKeyFromObject(org)}}
}

func (r *DexReconciler) discoverOIDCRedirectURL(ctx context.Context, org *greenhousesapv1alpha1.Organization) (string, error) {
func (r *OrganizationReconciler) discoverOIDCRedirectURL(ctx context.Context, org *greenhousesapv1alpha1.Organization) (string, error) {
if r := org.Spec.Authentication.OIDCConfig.RedirectURI; r != "" {
return r, nil
}
Expand All @@ -173,7 +117,7 @@ func (r *DexReconciler) discoverOIDCRedirectURL(ctx context.Context, org *greenh
return "", errors.New("oidc redirect URL not provided and cannot be discovered")
}

func (r *DexReconciler) reconcileOAuth2Client(ctx context.Context, org *greenhousesapv1alpha1.Organization) error {
func (r *OrganizationReconciler) reconcileOAuth2Client(ctx context.Context, org *greenhousesapv1alpha1.Organization) error {
var oAuth2Client = new(dexapi.OAuth2Client)
oAuth2Client.ObjectMeta.Name = encodedOAuth2ClientName(org.Name)
oAuth2Client.ObjectMeta.Namespace = r.Namespace
Expand Down
119 changes: 118 additions & 1 deletion pkg/controllers/organization/organization_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,19 @@ import (

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"

greenhousesapv1alpha1 "github.com/cloudoperators/greenhouse/pkg/apis/greenhouse/v1alpha1"
"github.com/cloudoperators/greenhouse/pkg/clientutil"
dexapi "github.com/cloudoperators/greenhouse/pkg/dex/api"
"github.com/cloudoperators/greenhouse/pkg/lifecycle"
"github.com/cloudoperators/greenhouse/pkg/scim"
)
Expand All @@ -25,21 +30,35 @@ var (
exposedConditions = []greenhousesapv1alpha1.ConditionType{
greenhousesapv1alpha1.ReadyCondition,
greenhousesapv1alpha1.SCIMAPIAvailableCondition,
greenhousesapv1alpha1.ServiceProxyProvisioned,
greenhousesapv1alpha1.OrganizationOICDConfigured,
greenhousesapv1alpha1.OrganizationAdminTeamConfigured,
greenhousesapv1alpha1.ServiceProxyProvisioned,
greenhousesapv1alpha1.OrganizationDefaultTeamRolesConfigured,
greenhousesapv1alpha1.NamespaceCreated,
greenhousesapv1alpha1.OrganizationRBACConfigured,
}
)

// OrganizationReconciler reconciles an Organization object
type OrganizationReconciler struct {
client.Client
recorder record.EventRecorder
recorder record.EventRecorder
Namespace string
}

//+kubebuilder:rbac:groups=greenhouse.sap,resources=organizations,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=greenhouse.sap,resources=organizations/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=greenhouse.sap,resources=organizations/finalizers,verbs=update
//+kubebuilder:rbac:groups=greenhouse.sap,resources=teams,verbs=get;watch;create;update;patch
//+kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=clusterroles;clusterrolebindings;roles;rolebindings,verbs=get;list;watch;create;update;patch
//+kubebuilder:rbac:groups=greenhouse.sap,resources=plugindefinitions,verbs=get;list;watch
//+kubebuilder:rbac:groups=greenhouse.sap,resources=plugins,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=greenhouse.sap,resources=teamroles,verbs=get;list;watch;create;update;patch
//+kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch;create;update;patch
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch
//+kubebuilder:rbac:groups=dex.coreos.com,resources=connectors;oauth2clients,verbs=get;list;watch;create;update;patch
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch

// SetupWithManager sets up the controller with the Manager.
func (r *OrganizationReconciler) SetupWithManager(name string, mgr ctrl.Manager) error {
Expand All @@ -50,6 +69,23 @@ func (r *OrganizationReconciler) SetupWithManager(name string, mgr ctrl.Manager)
For(&greenhousesapv1alpha1.Organization{}).
Owns(&corev1.Namespace{}).
Owns(&greenhousesapv1alpha1.Team{}).
Owns(&greenhousesapv1alpha1.TeamRole{}).
Owns(&greenhousesapv1alpha1.Plugin{}).
Owns(&rbacv1.Role{}).
Owns(&rbacv1.RoleBinding{}).
Owns(&rbacv1.ClusterRole{}).
Owns(&rbacv1.ClusterRoleBinding{}).
Owns(&dexapi.Connector{}).
Owns(&dexapi.OAuth2Client{}).
Watches(&corev1.Secret{},
handler.EnqueueRequestsFromMapFunc(r.enqueueOrganizationForReferencedSecret),
builder.WithPredicates(clientutil.PredicateHasOICDConfigured())).
Watches(&greenhousesapv1alpha1.PluginDefinition{},
handler.EnqueueRequestsFromMapFunc(r.enqueueAllOrganizationsForServiceProxyPluginDefinition),
builder.WithPredicates(predicate.And(
clientutil.PredicateByName(serviceProxyName),
predicate.GenerationChangedPredicate{},
))).
Complete(r)
}

Expand All @@ -70,12 +106,47 @@ func (r *OrganizationReconciler) EnsureCreated(ctx context.Context, object lifec
initOrganizationStatus(org)

if err := r.reconcileNamespace(ctx, org); err != nil {
org.SetCondition(greenhousesapv1alpha1.FalseCondition(greenhousesapv1alpha1.NamespaceCreated, "", err.Error()))
return ctrl.Result{}, lifecycle.Failed, err
}
org.SetCondition(greenhousesapv1alpha1.TrueCondition(greenhousesapv1alpha1.NamespaceCreated, "", ""))

if err := r.reconcileRBAC(ctx, org); err != nil {
org.SetCondition(greenhousesapv1alpha1.FalseCondition(greenhousesapv1alpha1.OrganizationRBACConfigured, "", err.Error()))
return ctrl.Result{}, lifecycle.Failed, err
}
org.SetCondition(greenhousesapv1alpha1.TrueCondition(greenhousesapv1alpha1.OrganizationRBACConfigured, "", ""))

if err := r.reconcileDefaultTeamRoles(ctx, org); err != nil {
org.SetCondition(greenhousesapv1alpha1.FalseCondition(greenhousesapv1alpha1.OrganizationDefaultTeamRolesConfigured, "", err.Error()))
return ctrl.Result{}, lifecycle.Failed, err
}
org.SetCondition(greenhousesapv1alpha1.TrueCondition(greenhousesapv1alpha1.OrganizationDefaultTeamRolesConfigured, "", ""))

if err := r.reconcileServiceProxy(ctx, org); err != nil {
org.SetCondition(greenhousesapv1alpha1.FalseCondition(greenhousesapv1alpha1.ServiceProxyProvisioned, "", err.Error()))
return ctrl.Result{}, lifecycle.Failed, err
}
org.SetCondition(greenhousesapv1alpha1.TrueCondition(greenhousesapv1alpha1.ServiceProxyProvisioned, "", ""))

if org.Spec.Authentication != nil && org.Spec.Authentication.OIDCConfig != nil {
if err := r.reconcileDexConnector(ctx, org); err != nil {
org.SetCondition(greenhousesapv1alpha1.FalseCondition(greenhousesapv1alpha1.OrganizationOICDConfigured, greenhousesapv1alpha1.DexReconcileFailed, ""))
return ctrl.Result{}, lifecycle.Failed, err
}

if err := r.reconcileOAuth2Client(ctx, org); err != nil {
org.SetCondition(greenhousesapv1alpha1.FalseCondition(greenhousesapv1alpha1.OrganizationOICDConfigured, greenhousesapv1alpha1.OAuthOICDFailed, err.Error()))
return ctrl.Result{}, lifecycle.Failed, err
}
org.SetCondition(greenhousesapv1alpha1.TrueCondition(greenhousesapv1alpha1.OrganizationOICDConfigured, "", ""))
}

if err := r.reconcileAdminTeam(ctx, org); err != nil {
org.SetCondition(greenhousesapv1alpha1.FalseCondition(greenhousesapv1alpha1.OrganizationAdminTeamConfigured, "", err.Error()))
return ctrl.Result{}, lifecycle.Failed, err
}
org.SetCondition(greenhousesapv1alpha1.TrueCondition(greenhousesapv1alpha1.OrganizationAdminTeamConfigured, "", ""))

return ctrl.Result{}, lifecycle.Success, nil
}
Expand Down Expand Up @@ -127,6 +198,52 @@ func (r *OrganizationReconciler) reconcileAdminTeam(ctx context.Context, org *gr
return nil
}

func (r *OrganizationReconciler) reconcileRBAC(ctx context.Context, org *greenhousesapv1alpha1.Organization) error {
// NOTE: The below code is intentionally rather explicit for transparency reasons as several Kubernetes resources
// are involved granting permissions on both cluster and namespace level based on organization, team membership and roles.
// The PolicyRules can be found in the pkg/rbac/role.

// RBAC for organization admins for cluster- and namespace-scoped resources.
if err := r.reconcileClusterRole(ctx, org, admin); err != nil {
return err
}
if err := r.reconcileClusterRoleBinding(ctx, org, admin); err != nil {
return err
}
if err := r.reconcileRole(ctx, org, admin); err != nil {
return err
}
if err := r.reconcileRoleBinding(ctx, org, admin); err != nil {
return err
}

// RBAC for organization members for cluster- and namespace-scoped resources.
if err := r.reconcileClusterRole(ctx, org, member); err != nil {
return err
}
if err := r.reconcileClusterRoleBinding(ctx, org, member); err != nil {
return err
}
if err := r.reconcileRole(ctx, org, member); err != nil {
return err
}
if err := r.reconcileRoleBinding(ctx, org, member); err != nil {
return err
}

// RBAC roles for organization cluster admins to access namespace-scoped resources.
if err := r.reconcileRole(ctx, org, clusterAdmin); err != nil {
return err
}

// RBAC roles for organization plugin admins to access namespace-scoped resources.
if err := r.reconcileRole(ctx, org, pluginAdmin); err != nil {
return err
}

return nil
}

func (r *OrganizationReconciler) checkSCIMAPIAvailability(ctx context.Context, org *greenhousesapv1alpha1.Organization) greenhousesapv1alpha1.Condition {
if org.Spec.Authentication == nil || org.Spec.Authentication.SCIMConfig == nil {
// SCIM Config is optional.
Expand Down
Loading

0 comments on commit 4431ae1

Please sign in to comment.