Skip to content

Commit 3321922

Browse files
committed
split mcp access management into smaller helper functions
1 parent 978a756 commit 3321922

File tree

2 files changed

+292
-184
lines changed

2 files changed

+292
-184
lines changed
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
package managedcontrolplane
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/util/sets"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
13+
14+
"github.com/openmcp-project/controller-utils/pkg/conditions"
15+
ctrlutils "github.com/openmcp-project/controller-utils/pkg/controller"
16+
errutils "github.com/openmcp-project/controller-utils/pkg/errors"
17+
"github.com/openmcp-project/controller-utils/pkg/logging"
18+
19+
clustersv1alpha1 "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1"
20+
cconst "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1/constants"
21+
commonapi "github.com/openmcp-project/openmcp-operator/api/common"
22+
apiconst "github.com/openmcp-project/openmcp-operator/api/constants"
23+
corev2alpha1 "github.com/openmcp-project/openmcp-operator/api/core/v2alpha1"
24+
libutils "github.com/openmcp-project/openmcp-operator/lib/utils"
25+
)
26+
27+
// manageAccessRequests aligns the existing AccessRequests for the MCP with the currently configured OIDC providers.
28+
// It uses the given createCon function to create conditions for AccessRequests and removes outdated AccessRequest related conditions directly from the MCP status.
29+
// The bool return value specifies whether everything related to MCP access is in the desired state or not. If 'false', it is recommended to requeue the MCP.
30+
func (r *ManagedControlPlaneReconciler) manageAccessRequests(ctx context.Context, mcp *corev2alpha1.ManagedControlPlane, cr *clustersv1alpha1.ClusterRequest, createCon func(conType string, status metav1.ConditionStatus, reason, message string)) (bool, errutils.ReasonableError) {
31+
updatedAccessRequests, rerr := r.createOrUpdateDesiredAccessRequests(ctx, mcp, cr, createCon)
32+
if rerr != nil {
33+
return false, rerr
34+
}
35+
36+
accessRequestsInDeletion, rerr := r.deleteUndesiredAccessRequests(ctx, mcp, updatedAccessRequests, createCon)
37+
if rerr != nil {
38+
return false, rerr
39+
}
40+
41+
allAccessReady, rerr := r.syncAccessSecrets(ctx, mcp, updatedAccessRequests, createCon)
42+
if rerr != nil {
43+
return false, rerr
44+
}
45+
46+
accessSecretsInDeletion, rerr := r.deleteUndesiredAccessSecrets(ctx, mcp, updatedAccessRequests, createCon)
47+
if rerr != nil {
48+
return false, rerr
49+
}
50+
51+
// remove conditions for AccessRequests that are neither required nor in deletion (= have been deleted already)
52+
cu := conditions.ConditionUpdater(mcp.Status.Conditions, false).WithEventRecorder(r.eventRecorder, conditions.EventPerChange)
53+
for _, con := range mcp.Status.Conditions {
54+
if !strings.HasPrefix(con.Type, corev2alpha1.ConditionPrefixOIDCAccessReady) {
55+
continue
56+
}
57+
providerName := strings.TrimPrefix(con.Type, corev2alpha1.ConditionPrefixOIDCAccessReady)
58+
if _, ok := updatedAccessRequests[providerName]; !ok && !accessRequestsInDeletion.Has(providerName) {
59+
cu.RemoveCondition(corev2alpha1.ConditionPrefixOIDCAccessReady + providerName)
60+
}
61+
}
62+
mcp.Status.Conditions, _ = cu.Record(mcp).Conditions()
63+
64+
everythingReady := accessRequestsInDeletion.Len() == 0 && accessSecretsInDeletion.Len() == 0 && allAccessReady
65+
if everythingReady {
66+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionTrue, "", "All accesses are ready")
67+
} else {
68+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "Not all accesses are ready")
69+
}
70+
71+
return everythingReady, nil
72+
}
73+
74+
// createOrUpdateDesiredAccessRequests creates/updates all AccessRequests that are desired according to the ManagedControlPlane's configured OIDC providers.
75+
// It returns a mapping from OIDC provider names to the corresponding AccessRequests.
76+
// If the ManagedControlPlane has a non-zero DeletionTimestamp, no AccessRequests will be created or updated and the returned map will be empty.
77+
func (r *ManagedControlPlaneReconciler) createOrUpdateDesiredAccessRequests(ctx context.Context, mcp *corev2alpha1.ManagedControlPlane, cr *clustersv1alpha1.ClusterRequest, createCon func(conType string, status metav1.ConditionStatus, reason, message string)) (map[string]*clustersv1alpha1.AccessRequest, errutils.ReasonableError) {
78+
log := logging.FromContextOrPanic(ctx)
79+
80+
namespace := libutils.StableRequestNamespace(mcp.Namespace)
81+
updatedAccessRequests := map[string]*clustersv1alpha1.AccessRequest{}
82+
var oidcProviders []*commonapi.OIDCProviderConfig
83+
84+
// create or update AccessRequests for the ManagedControlPlane
85+
if mcp.DeletionTimestamp.IsZero() {
86+
oidcProviders = make([]*commonapi.OIDCProviderConfig, 0, len(mcp.Spec.IAM.OIDCProviders)+1)
87+
if r.Config.StandardOIDCProvider != nil && len(mcp.Spec.IAM.RoleBindings) > 0 {
88+
// add default OIDC provider, unless it has been disabled
89+
defaultOidc := r.Config.StandardOIDCProvider.DeepCopy()
90+
defaultOidc.Name = corev2alpha1.DefaultOIDCProviderName
91+
defaultOidc.RoleBindings = mcp.Spec.IAM.RoleBindings
92+
oidcProviders = append(oidcProviders, defaultOidc)
93+
}
94+
oidcProviders = append(oidcProviders, mcp.Spec.IAM.OIDCProviders...)
95+
}
96+
97+
for _, oidc := range oidcProviders {
98+
log.Debug("Creating/updating AccessRequest for OIDC provider", "oidcProviderName", oidc.Name)
99+
arName := ctrlutils.K8sNameHash(mcp.Name, oidc.Name)
100+
ar := &clustersv1alpha1.AccessRequest{}
101+
ar.Name = arName
102+
ar.Namespace = namespace
103+
if _, err := controllerutil.CreateOrUpdate(ctx, r.PlatformCluster.Client(), ar, func() error {
104+
ar.Spec.RequestRef = &commonapi.ObjectReference{
105+
Name: cr.Name,
106+
Namespace: cr.Namespace,
107+
}
108+
ar.Spec.OIDCProvider = oidc
109+
110+
// set labels
111+
if ar.Labels == nil {
112+
ar.Labels = map[string]string{}
113+
}
114+
ar.Labels[corev2alpha1.MCPLabel] = mcp.Name
115+
ar.Labels[apiconst.ManagedByLabel] = ControllerName
116+
ar.Labels[corev2alpha1.OIDCProviderLabel] = corev2alpha1.DefaultOIDCProviderName
117+
118+
return nil
119+
}); err != nil {
120+
rerr := errutils.WithReason(fmt.Errorf("error creating/updating AccessRequest '%s/%s': %w", ar.Namespace, ar.Name, err), cconst.ReasonPlatformClusterInteractionProblem)
121+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+oidc.Name, metav1.ConditionFalse, rerr.Reason(), rerr.Error())
122+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "Error creating/updating AccessRequest for OIDC provider "+oidc.Name)
123+
return nil, rerr
124+
}
125+
updatedAccessRequests[corev2alpha1.DefaultOIDCProviderName] = ar
126+
}
127+
128+
return updatedAccessRequests, nil
129+
}
130+
131+
// deleteUndesiredAccessRequests deletes all AccessRequests that belong to the given ManagedControlPlane, but are not in the updatedAccessRequests map.
132+
// These are AccessRequests that have been created for a previous version of the ManagedControlPlane and are not needed anymore.
133+
// It returns a set of OIDC provider names for which the AccessRequests are still in deletion. If the set is empty, all undesired AccessRequests have been deleted.
134+
func (r *ManagedControlPlaneReconciler) deleteUndesiredAccessRequests(ctx context.Context, mcp *corev2alpha1.ManagedControlPlane, updatedAccessRequests map[string]*clustersv1alpha1.AccessRequest, createCon func(conType string, status metav1.ConditionStatus, reason, message string)) (sets.Set[string], errutils.ReasonableError) {
135+
log := logging.FromContextOrPanic(ctx)
136+
137+
namespace := libutils.StableRequestNamespace(mcp.Namespace)
138+
accessRequestsInDeletion := sets.New[string]()
139+
140+
// delete all AccessRequests that have previously been created for this ManagedControlPlane but are not needed anymore
141+
oidcARs := &clustersv1alpha1.AccessRequestList{}
142+
if err := r.PlatformCluster.Client().List(ctx, oidcARs, client.InNamespace(namespace), client.HasLabels{corev2alpha1.OIDCProviderLabel}, client.MatchingLabels{
143+
corev2alpha1.MCPLabel: mcp.Name,
144+
apiconst.ManagedByLabel: ControllerName,
145+
}); err != nil {
146+
rerr := errutils.WithReason(fmt.Errorf("error listing AccessRequests for ManagedControlPlane '%s/%s': %w", mcp.Namespace, mcp.Name, err), cconst.ReasonPlatformClusterInteractionProblem)
147+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionFalse, rerr.Reason(), rerr.Error())
148+
return accessRequestsInDeletion, rerr
149+
}
150+
errs := errutils.NewReasonableErrorList()
151+
for _, ar := range oidcARs.Items {
152+
if _, ok := updatedAccessRequests[ar.Spec.OIDCProvider.Name]; ok {
153+
continue
154+
}
155+
providerName := "<unknown>"
156+
if ar.Spec.OIDCProvider != nil {
157+
providerName = ar.Spec.OIDCProvider.Name
158+
}
159+
accessRequestsInDeletion.Insert(ar.Name)
160+
if !ar.DeletionTimestamp.IsZero() {
161+
log.Debug("Waiting for deletion of AccessRequest that is no longer required", "accessRequestName", ar.Name, "accessRequestNamespace", ar.Namespace, "oidcProviderName", providerName)
162+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "AccessRequest is being deleted")
163+
continue
164+
}
165+
log.Debug("Deleting AccessRequest that is no longer needed", "accessRequestName", ar.Name, "accessRequestNamespace", ar.Namespace, "oidcProviderName", providerName)
166+
if err := r.PlatformCluster.Client().Delete(ctx, &ar); client.IgnoreNotFound(err) != nil {
167+
rerr := errutils.WithReason(fmt.Errorf("error deleting AccessRequest '%s/%s': %w", ar.Namespace, ar.Name, err), cconst.ReasonPlatformClusterInteractionProblem)
168+
errs.Append(rerr)
169+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, rerr.Reason(), rerr.Error())
170+
}
171+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "AccessRequest is being deleted")
172+
}
173+
if rerr := errs.Aggregate(); rerr != nil {
174+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "Error deleting AccessRequests that are no longer needed")
175+
return accessRequestsInDeletion, rerr
176+
}
177+
178+
return accessRequestsInDeletion, nil
179+
}
180+
181+
// deleteUndesiredAccessSecrets deletes all access secrets belonging to the ManagedControlPlane that are not copied from an up-to-date AccessRequest.
182+
// It returns a set of OIDC provider names for which the AccessRequest secrets are still in deletion.
183+
func (r *ManagedControlPlaneReconciler) deleteUndesiredAccessSecrets(ctx context.Context, mcp *corev2alpha1.ManagedControlPlane, updatedAccessRequests map[string]*clustersv1alpha1.AccessRequest, createCon func(conType string, status metav1.ConditionStatus, reason, message string)) (sets.Set[string], errutils.ReasonableError) {
184+
log := logging.FromContextOrPanic(ctx)
185+
186+
accessSecretsInDeletion := sets.New[string]()
187+
188+
// delete all AccessRequest secrets that have been copied to the Onboarding cluster and belong to AccessRequests that are no longer needed
189+
mcpSecrets := &corev1.SecretList{}
190+
if err := r.OnboardingCluster.Client().List(ctx, mcpSecrets, client.InNamespace(mcp.Namespace), client.HasLabels{corev2alpha1.OIDCProviderLabel}, client.MatchingLabels{
191+
corev2alpha1.MCPLabel: mcp.Name,
192+
apiconst.ManagedByLabel: ControllerName,
193+
}); err != nil {
194+
rerr := errutils.WithReason(fmt.Errorf("error listing secrets for ManagedControlPlane '%s/%s': %w", mcp.Namespace, mcp.Name, err), cconst.ReasonOnboardingClusterInteractionProblem)
195+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionFalse, rerr.Reason(), rerr.Error())
196+
return accessSecretsInDeletion, rerr
197+
}
198+
199+
errs := errutils.NewReasonableErrorList()
200+
for _, mcpSecret := range mcpSecrets.Items {
201+
providerName := mcpSecret.Labels[corev2alpha1.OIDCProviderLabel]
202+
if providerName == "" {
203+
log.Error(nil, "Secret for ManagedControlPlane has an empty OIDCProvider label, this should not happen", "secretName", mcpSecret.Name, "secretNamespace", mcpSecret.Namespace)
204+
continue
205+
}
206+
if _, ok := updatedAccessRequests[providerName]; ok {
207+
continue
208+
}
209+
accessSecretsInDeletion.Insert(providerName)
210+
if !mcpSecret.DeletionTimestamp.IsZero() {
211+
log.Debug("Waiting for deletion of access secret that is no longer required", "secretName", mcpSecret.Name, "secretNamespace", mcpSecret.Namespace, "oidcProviderName", providerName)
212+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "AccessRequest secret is being deleted")
213+
continue
214+
}
215+
log.Debug("Deleting access secret that is no longer required", "secretName", mcpSecret.Name, "secretNamespace", mcpSecret.Namespace, "oidcProviderName", providerName)
216+
if err := r.OnboardingCluster.Client().Delete(ctx, &mcpSecret); client.IgnoreNotFound(err) != nil {
217+
rerr := errutils.WithReason(fmt.Errorf("error deleting access secret '%s/%s': %w", mcpSecret.Namespace, mcpSecret.Name, err), cconst.ReasonOnboardingClusterInteractionProblem)
218+
errs.Append(rerr)
219+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, rerr.Reason(), rerr.Error())
220+
}
221+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "access secret is being deleted")
222+
}
223+
if rerr := errs.Aggregate(); rerr != nil {
224+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "Error deleting access secrets that are no longer needed")
225+
return accessSecretsInDeletion, rerr
226+
}
227+
228+
return accessSecretsInDeletion, nil
229+
}
230+
231+
// syncAccessSecrets checks if all AccessRequests belonging to the ManagedControlPlane are ready and copies their secrets to the Onboarding cluster and references them in the ManagedControlPlane status.
232+
// It returns a boolean indicating whether all AccessRequests are ready and their secrets have been copied successfully (true) or not (false).
233+
func (r *ManagedControlPlaneReconciler) syncAccessSecrets(ctx context.Context, mcp *corev2alpha1.ManagedControlPlane, updatedAccessRequests map[string]*clustersv1alpha1.AccessRequest, createCon func(conType string, status metav1.ConditionStatus, reason, message string)) (bool, errutils.ReasonableError) {
234+
log := logging.FromContextOrPanic(ctx)
235+
236+
allAccessReady := true
237+
if mcp.Status.Access == nil {
238+
mcp.Status.Access = map[string]commonapi.LocalObjectReference{}
239+
}
240+
for providerName, ar := range updatedAccessRequests {
241+
if !ar.Status.IsGranted() || ar.Status.SecretRef == nil {
242+
log.Debug("AccessRequest is not ready yet", "accessRequestName", ar.Name, "accessRequestNamespace", ar.Namespace, "oidcProviderName", providerName)
243+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "AccessRequest is not ready yet")
244+
allAccessReady = false
245+
} else {
246+
// copy access request secret and reference it in the ManagedControlPlane status
247+
arSecret := &corev1.Secret{}
248+
arSecret.Name = ar.Status.SecretRef.Name
249+
arSecret.Namespace = ar.Status.SecretRef.Namespace
250+
if err := r.PlatformCluster.Client().Get(ctx, client.ObjectKeyFromObject(arSecret), arSecret); err != nil {
251+
rerr := errutils.WithReason(fmt.Errorf("error getting AccessRequest secret '%s/%s': %w", arSecret.Namespace, arSecret.Name, err), cconst.ReasonPlatformClusterInteractionProblem)
252+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, rerr.Reason(), rerr.Error())
253+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "Error getting AccessRequest secret for OIDC provider "+providerName)
254+
return false, rerr
255+
}
256+
mcpSecret := &corev1.Secret{}
257+
mcpSecret.Name = ctrlutils.K8sNameHash(mcp.Name, providerName)
258+
mcpSecret.Namespace = mcp.Namespace
259+
if _, err := controllerutil.CreateOrUpdate(ctx, r.OnboardingCluster.Client(), mcpSecret, func() error {
260+
mcpSecret.Data = arSecret.Data
261+
if mcpSecret.Labels == nil {
262+
mcpSecret.Labels = map[string]string{}
263+
}
264+
mcpSecret.Labels[corev2alpha1.MCPLabel] = mcp.Name
265+
mcpSecret.Labels[corev2alpha1.OIDCProviderLabel] = providerName
266+
mcpSecret.Labels[apiconst.ManagedByLabel] = ControllerName
267+
268+
if err := controllerutil.SetControllerReference(mcp, mcpSecret, r.OnboardingCluster.Scheme()); err != nil {
269+
return err
270+
}
271+
return nil
272+
}); err != nil {
273+
rerr := errutils.WithReason(fmt.Errorf("error creating/updating AccessRequest secret '%s/%s': %w", mcpSecret.Namespace, mcpSecret.Name, err), cconst.ReasonOnboardingClusterInteractionProblem)
274+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionFalse, rerr.Reason(), rerr.Error())
275+
createCon(corev2alpha1.ConditionAllAccessReady, metav1.ConditionFalse, cconst.ReasonWaitingForAccessRequest, "Error creating/updating AccessRequest secret for OIDC provider "+providerName)
276+
return false, rerr
277+
}
278+
log.Debug("Access secret for ManagedControlPlane created/updated", "secretName", mcpSecret.Name, "oidcProviderName", providerName)
279+
mcp.Status.Access[providerName] = commonapi.LocalObjectReference{
280+
Name: mcpSecret.Name,
281+
}
282+
createCon(corev2alpha1.ConditionPrefixOIDCAccessReady+providerName, metav1.ConditionTrue, "", "")
283+
}
284+
}
285+
286+
return allAccessReady, nil
287+
}

0 commit comments

Comments
 (0)