mirror of
https://github.com/argoproj/argo-cd
synced 2026-04-21 17:07:16 +00:00
fix: prevent panic on nil APIResource in permission validator (#26610)
Signed-off-by: Andy Lo-A-Foe <andy.loafoe@gmail.com>
This commit is contained in:
parent
87ccebc51a
commit
442aed496f
2 changed files with 147 additions and 16 deletions
|
|
@ -308,22 +308,9 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, project *v1alp
|
|||
sync.WithLogr(logutils.NewLogrusLogger(logEntry)),
|
||||
sync.WithHealthOverride(lua.ResourceHealthOverrides(resourceOverrides)),
|
||||
sync.WithPermissionValidator(func(un *unstructured.Unstructured, res *metav1.APIResource) error {
|
||||
if !project.IsGroupKindNamePermitted(un.GroupVersionKind().GroupKind(), un.GetName(), res.Namespaced) {
|
||||
return fmt.Errorf("resource %s:%s is not permitted in project %s", un.GroupVersionKind().Group, un.GroupVersionKind().Kind, project.Name)
|
||||
}
|
||||
if res.Namespaced {
|
||||
permitted, err := project.IsDestinationPermitted(destCluster, un.GetNamespace(), func(project string) ([]*v1alpha1.Cluster, error) {
|
||||
return m.db.GetProjectClusters(context.TODO(), project)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !permitted {
|
||||
return fmt.Errorf("namespace %v is not permitted in project '%s'", un.GetNamespace(), project.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return validateSyncPermissions(project, destCluster, func(proj string) ([]*v1alpha1.Cluster, error) {
|
||||
return m.db.GetProjectClusters(context.TODO(), proj)
|
||||
}, un, res)
|
||||
}),
|
||||
sync.WithOperationSettings(syncOp.DryRun, syncOp.Prune, syncOp.SyncStrategy.Force(), syncOp.IsApplyStrategy() || len(syncOp.Resources) > 0),
|
||||
sync.WithInitialState(state.Phase, state.Message, initialResourcesRes, state.StartedAt),
|
||||
|
|
@ -605,3 +592,33 @@ func deriveServiceAccountToImpersonate(project *v1alpha1.AppProject, application
|
|||
// if there is no match found in the AppProject.Spec.DestinationServiceAccounts, use the default service account of the destination namespace.
|
||||
return "", fmt.Errorf("no matching service account found for destination server %s and namespace %s", application.Spec.Destination.Server, serviceAccountNamespace)
|
||||
}
|
||||
|
||||
// validateSyncPermissions checks whether the given resource is permitted by the project's
|
||||
// allow/deny lists and destination rules. It returns an error if the API resource info is nil
|
||||
// (preventing a nil-pointer panic), if the resource's group/kind is not permitted, or if
|
||||
// the resource's namespace is not an allowed destination.
|
||||
func validateSyncPermissions(
|
||||
project *v1alpha1.AppProject,
|
||||
destCluster *v1alpha1.Cluster,
|
||||
getProjectClusters func(string) ([]*v1alpha1.Cluster, error),
|
||||
un *unstructured.Unstructured,
|
||||
res *metav1.APIResource,
|
||||
) error {
|
||||
if res == nil {
|
||||
return fmt.Errorf("failed to get API resource info for %s/%s: unable to verify permissions", un.GroupVersionKind().Group, un.GroupVersionKind().Kind)
|
||||
}
|
||||
if !project.IsGroupKindNamePermitted(un.GroupVersionKind().GroupKind(), un.GetName(), res.Namespaced) {
|
||||
return fmt.Errorf("resource %s:%s is not permitted in project %s", un.GroupVersionKind().Group, un.GroupVersionKind().Kind, project.Name)
|
||||
}
|
||||
if res.Namespaced {
|
||||
permitted, err := project.IsDestinationPermitted(destCluster, un.GetNamespace(), getProjectClusters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !permitted {
|
||||
return fmt.Errorf("namespace %v is not permitted in project '%s'", un.GetNamespace(), project.Name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/argoproj/argo-cd/v3/common"
|
||||
"github.com/argoproj/argo-cd/v3/controller/testdata"
|
||||
|
|
@ -1653,3 +1654,116 @@ func dig(obj any, path ...any) any {
|
|||
|
||||
return i
|
||||
}
|
||||
|
||||
func TestValidateSyncPermissions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
newResource := func(group, kind, name, namespace string) *unstructured.Unstructured {
|
||||
obj := &unstructured.Unstructured{}
|
||||
obj.SetGroupVersionKind(schema.GroupVersionKind{Group: group, Version: "v1", Kind: kind})
|
||||
obj.SetName(name)
|
||||
obj.SetNamespace(namespace)
|
||||
return obj
|
||||
}
|
||||
|
||||
project := &v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-project",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
Destinations: []v1alpha1.ApplicationDestination{
|
||||
{Namespace: "default", Server: "*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
destCluster := &v1alpha1.Cluster{
|
||||
Server: "https://kubernetes.default.svc",
|
||||
}
|
||||
|
||||
noopGetClusters := func(_ string) ([]*v1alpha1.Cluster, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
t.Run("nil APIResource returns error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
un := newResource("apps", "Deployment", "my-deploy", "default")
|
||||
|
||||
err := validateSyncPermissions(project, destCluster, noopGetClusters, un, nil)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to get API resource info for apps/Deployment")
|
||||
assert.Contains(t, err.Error(), "unable to verify permissions")
|
||||
})
|
||||
|
||||
t.Run("permitted namespaced resource returns no error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
un := newResource("", "ConfigMap", "my-cm", "default")
|
||||
res := &metav1.APIResource{Name: "configmaps", Namespaced: true}
|
||||
|
||||
err := validateSyncPermissions(project, destCluster, noopGetClusters, un, res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("group kind not permitted returns error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
projectWithDenyList := &v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "restricted-project",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
Destinations: []v1alpha1.ApplicationDestination{
|
||||
{Namespace: "*", Server: "*"},
|
||||
},
|
||||
ClusterResourceBlacklist: []v1alpha1.ClusterResourceRestrictionItem{
|
||||
{Group: "rbac.authorization.k8s.io", Kind: "ClusterRole"},
|
||||
},
|
||||
},
|
||||
}
|
||||
un := newResource("rbac.authorization.k8s.io", "ClusterRole", "my-role", "")
|
||||
res := &metav1.APIResource{Name: "clusterroles", Namespaced: false}
|
||||
|
||||
err := validateSyncPermissions(projectWithDenyList, destCluster, noopGetClusters, un, res)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "is not permitted in project")
|
||||
})
|
||||
|
||||
t.Run("namespace not permitted returns error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
un := newResource("", "ConfigMap", "my-cm", "kube-system")
|
||||
res := &metav1.APIResource{Name: "configmaps", Namespaced: true}
|
||||
|
||||
err := validateSyncPermissions(project, destCluster, noopGetClusters, un, res)
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "namespace kube-system is not permitted in project")
|
||||
})
|
||||
|
||||
t.Run("cluster-scoped resource skips namespace check", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
projectWithClusterResources := &v1alpha1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-project",
|
||||
Namespace: "argocd",
|
||||
},
|
||||
Spec: v1alpha1.AppProjectSpec{
|
||||
Destinations: []v1alpha1.ApplicationDestination{
|
||||
{Namespace: "default", Server: "*"},
|
||||
},
|
||||
ClusterResourceWhitelist: []v1alpha1.ClusterResourceRestrictionItem{
|
||||
{Group: "*", Kind: "*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
un := newResource("", "Namespace", "my-ns", "")
|
||||
res := &metav1.APIResource{Name: "namespaces", Namespaced: false}
|
||||
|
||||
err := validateSyncPermissions(projectWithClusterResources, destCluster, noopGetClusters, un, res)
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue