mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
feat: Add impersonation support for App finalizer deletion (#24524)
Signed-off-by: Charles Coupal-Jetté <charles.coupaljette@goto.com> Signed-off-by: Charles Coupal-Jetté <83649150+ccjette-logmein@users.noreply.github.com> Co-authored-by: Alexandre Gaudreault <alexandre_gaudreault@intuit.com>
This commit is contained in:
committed by
GitHub
parent
1db5ee809c
commit
8c890d4285
@@ -39,6 +39,7 @@ import (
|
||||
"k8s.io/client-go/informers"
|
||||
informerv1 "k8s.io/client-go/informers/apps/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/utils/ptr"
|
||||
@@ -1219,6 +1220,11 @@ func (ctrl *ApplicationController) finalizeApplicationDeletion(app *appv1.Applic
|
||||
}
|
||||
config := metrics.AddMetricsTransportWrapper(ctrl.metricsServer, app, clusterRESTConfig)
|
||||
|
||||
// Apply impersonation config if necessary
|
||||
if err := ctrl.applyImpersonationConfig(config, proj, app, destCluster); err != nil {
|
||||
return fmt.Errorf("cannot apply impersonation: %w", err)
|
||||
}
|
||||
|
||||
if app.CascadedDeletion() {
|
||||
deletionApproved := app.IsDeletionConfirmed(app.DeletionTimestamp.Time)
|
||||
|
||||
@@ -2616,4 +2622,22 @@ func (ctrl *ApplicationController) logAppEvent(ctx context.Context, a *appv1.App
|
||||
ctrl.auditLogger.LogAppEvent(a, eventInfo, message, "", eventLabels)
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) applyImpersonationConfig(config *rest.Config, proj *appv1.AppProject, app *appv1.Application, destCluster *appv1.Cluster) error {
|
||||
impersonationEnabled, err := ctrl.settingsMgr.IsImpersonationEnabled()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting impersonation setting: %w", err)
|
||||
}
|
||||
if !impersonationEnabled {
|
||||
return nil
|
||||
}
|
||||
user, err := deriveServiceAccountToImpersonate(proj, app, destCluster)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error deriving service account to impersonate: %w", err)
|
||||
}
|
||||
config.Impersonate = rest.ImpersonationConfig{
|
||||
UserName: user,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ClusterFilterFunction func(c *appv1.Cluster, distributionFunction sharding.DistributionFunction) bool
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -1246,6 +1247,117 @@ func TestFinalizeAppDeletion(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestFinalizeAppDeletionWithImpersonation(t *testing.T) {
|
||||
type fixture struct {
|
||||
application *v1alpha1.Application
|
||||
controller *ApplicationController
|
||||
}
|
||||
|
||||
setup := func(destinationNamespace, serviceAccountName string) *fixture {
|
||||
app := newFakeApp()
|
||||
app.Status.OperationState = nil
|
||||
app.Status.History = nil
|
||||
now := metav1.Now()
|
||||
app.DeletionTimestamp = &now
|
||||
|
||||
project := &v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
Name: "default",
|
||||
},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
SourceRepos: []string{"*"},
|
||||
Destinations: []v1alpha1.ApplicationDestination{
|
||||
{
|
||||
Server: "*",
|
||||
Namespace: "*",
|
||||
},
|
||||
},
|
||||
DestinationServiceAccounts: []v1alpha1.ApplicationDestinationServiceAccount{
|
||||
{
|
||||
Server: "https://localhost:6443",
|
||||
Namespace: destinationNamespace,
|
||||
DefaultServiceAccount: serviceAccountName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
additionalObjs := []runtime.Object{}
|
||||
if serviceAccountName != "" {
|
||||
syncServiceAccount := &corev1.ServiceAccount{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: serviceAccountName,
|
||||
Namespace: test.FakeDestNamespace,
|
||||
},
|
||||
}
|
||||
additionalObjs = append(additionalObjs, syncServiceAccount)
|
||||
}
|
||||
|
||||
data := fakeData{
|
||||
apps: []runtime.Object{app, project},
|
||||
manifestResponse: &apiclient.ManifestResponse{
|
||||
Manifests: []string{},
|
||||
Namespace: test.FakeDestNamespace,
|
||||
Server: "https://localhost:6443",
|
||||
Revision: "abc123",
|
||||
},
|
||||
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{},
|
||||
configMapData: map[string]string{
|
||||
"application.sync.impersonation.enabled": strconv.FormatBool(true),
|
||||
},
|
||||
additionalObjs: additionalObjs,
|
||||
}
|
||||
ctrl := newFakeController(&data, nil)
|
||||
return &fixture{
|
||||
application: app,
|
||||
controller: ctrl,
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("no matching impersonation service account is configured", func(t *testing.T) {
|
||||
// given impersonation is enabled but no matching service account exists
|
||||
f := setup(test.FakeDestNamespace, "")
|
||||
|
||||
// when
|
||||
err := f.controller.finalizeApplicationDeletion(f.application, func(_ string) ([]*v1alpha1.Cluster, error) {
|
||||
return []*v1alpha1.Cluster{}, nil
|
||||
})
|
||||
|
||||
// then deletion should fail due to impersonation error
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "error deriving service account to impersonate")
|
||||
})
|
||||
|
||||
t.Run("valid impersonation service account is configured", func(t *testing.T) {
|
||||
// given impersonation is enabled with valid service account
|
||||
f := setup(test.FakeDestNamespace, "test-sa")
|
||||
|
||||
// when
|
||||
err := f.controller.finalizeApplicationDeletion(f.application, func(_ string) ([]*v1alpha1.Cluster, error) {
|
||||
return []*v1alpha1.Cluster{}, nil
|
||||
})
|
||||
|
||||
// then deletion should succeed
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid application destination cluster", func(t *testing.T) {
|
||||
// given impersonation is enabled but destination cluster does not exist
|
||||
f := setup(test.FakeDestNamespace, "test-sa")
|
||||
f.application.Spec.Destination.Server = "https://invalid-cluster:6443"
|
||||
f.application.Spec.Destination.Name = "invalid"
|
||||
|
||||
// when
|
||||
err := f.controller.finalizeApplicationDeletion(f.application, func(_ string) ([]*v1alpha1.Cluster, error) {
|
||||
return []*v1alpha1.Cluster{}, nil
|
||||
})
|
||||
|
||||
// then deletion should still succeed by removing finalizers
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
// TestNormalizeApplication verifies we normalize an application during reconciliation
|
||||
func TestNormalizeApplication(t *testing.T) {
|
||||
defaultProj := v1alpha1.AppProject{
|
||||
|
||||
Reference in New Issue
Block a user