From 8aa3bfc59c8940fb209382471bbdb06864174802 Mon Sep 17 00:00:00 2001 From: Dmitry Salakhov Date: Tue, 20 Jul 2021 14:05:31 +1200 Subject: [PATCH] fix(namespace): update portainer-config when delete a namespace (#5330) --- api/go.sum | 7 ++ .../proxy/factory/kubernetes/namespaces.go | 5 ++ api/kubernetes/cli/access.go | 20 +++++- api/kubernetes/cli/access_test.go | 68 +++++++++++++++++++ api/kubernetes/cli/client.go | 5 +- api/portainer.go | 1 + 6 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 api/kubernetes/cli/access_test.go diff --git a/api/go.sum b/api/go.sum index b7e4bb5c2..37a687dde 100644 --- a/api/go.sum +++ b/api/go.sum @@ -80,6 +80,7 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -153,6 +154,7 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= @@ -219,8 +221,10 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420 h1:Yu3681ykYHDfLoI6XVjL4JWmkE+3TX9yfIWwRCh1kFM= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= @@ -366,9 +370,11 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= @@ -395,6 +401,7 @@ k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUc k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo= k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= diff --git a/api/http/proxy/factory/kubernetes/namespaces.go b/api/http/proxy/factory/kubernetes/namespaces.go index 3272649d7..daa71749f 100644 --- a/api/http/proxy/factory/kubernetes/namespaces.go +++ b/api/http/proxy/factory/kubernetes/namespaces.go @@ -3,10 +3,15 @@ package kubernetes import ( "net/http" + "github.com/pkg/errors" portainer "github.com/portainer/portainer/api" ) func (transport *baseTransport) proxyNamespaceDeleteOperation(request *http.Request, namespace string) (*http.Response, error) { + if err := transport.tokenManager.kubecli.NamespaceAccessPoliciesDeleteNamespace(namespace); err != nil { + return nil, errors.WithMessagef(err, "failed to delete a namespace [%s] from portainer config", namespace) + } + registries, err := transport.dataStore.Registry().Registries() if err != nil { return nil, err diff --git a/api/kubernetes/cli/access.go b/api/kubernetes/cli/access.go index af5fa887a..e8725292f 100644 --- a/api/kubernetes/cli/access.go +++ b/api/kubernetes/cli/access.go @@ -3,6 +3,7 @@ package cli import ( "encoding/json" + "github.com/pkg/errors" portainer "github.com/portainer/portainer/api" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -12,11 +13,24 @@ type ( namespaceAccessPolicies map[string]portainer.K8sNamespaceAccessPolicy ) +// NamespaceAccessPoliciesDeleteNamespace removes stored policies associated with a given namespace +func (kcl *KubeClient) NamespaceAccessPoliciesDeleteNamespace(ns string) error { + kcl.lock.Lock() + defer kcl.lock.Unlock() + + policies, err := kcl.GetNamespaceAccessPolicies() + if err != nil { + return errors.WithMessage(err, "failed to fetch access policies") + } + + delete(policies, ns) + + return kcl.UpdateNamespaceAccessPolicies(policies) +} + // GetNamespaceAccessPolicies gets the namespace access policies // from config maps in the portainer namespace -func (kcl *KubeClient) GetNamespaceAccessPolicies() ( - map[string]portainer.K8sNamespaceAccessPolicy, error, -) { +func (kcl *KubeClient) GetNamespaceAccessPolicies() (map[string]portainer.K8sNamespaceAccessPolicy, error) { configMap, err := kcl.cli.CoreV1().ConfigMaps(portainerNamespace).Get(portainerConfigMapName, metav1.GetOptions{}) if k8serrors.IsNotFound(err) { return nil, nil diff --git a/api/kubernetes/cli/access_test.go b/api/kubernetes/cli/access_test.go new file mode 100644 index 000000000..b43d06dc6 --- /dev/null +++ b/api/kubernetes/cli/access_test.go @@ -0,0 +1,68 @@ +package cli + +import ( + "sync" + "testing" + + portainer "github.com/portainer/portainer/api" + "github.com/stretchr/testify/assert" + ktypes "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kfake "k8s.io/client-go/kubernetes/fake" +) + +func Test_NamespaceAccessPoliciesDeleteNamespace_updatesPortainerConfig_whenConfigExists(t *testing.T) { + testcases := []struct { + name string + namespaceToDelete string + expectedConfig map[string]portainer.K8sNamespaceAccessPolicy + }{ + { + name: "doesn't change config, when designated namespace absent", + namespaceToDelete: "missing-namespace", + expectedConfig: map[string]portainer.K8sNamespaceAccessPolicy{ + "ns1": {UserAccessPolicies: portainer.UserAccessPolicies{2: {RoleID: 0}}}, + "ns2": {UserAccessPolicies: portainer.UserAccessPolicies{2: {RoleID: 0}}}, + }, + }, + { + name: "removes designated namespace from config, when namespace is present", + namespaceToDelete: "ns2", + expectedConfig: map[string]portainer.K8sNamespaceAccessPolicy{ + "ns1": {UserAccessPolicies: portainer.UserAccessPolicies{2: {RoleID: 0}}}, + }, + }, + } + + for _, test := range testcases { + t.Run(test.name, func(t *testing.T) { + k := &KubeClient{ + cli: kfake.NewSimpleClientset(), + instanceID: "instance", + lock: &sync.Mutex{}, + } + + config := &ktypes.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: portainerConfigMapName, + Namespace: portainerNamespace, + }, + Data: map[string]string{ + "NamespaceAccessPolicies": `{"ns1":{"UserAccessPolicies":{"2":{"RoleId":0}}}, "ns2":{"UserAccessPolicies":{"2":{"RoleId":0}}}}`, + }, + } + _, err := k.cli.CoreV1().ConfigMaps(portainerNamespace).Create(config) + assert.NoError(t, err, "failed to create a portainer config") + defer func() { + k.cli.CoreV1().ConfigMaps(portainerNamespace).Delete(portainerConfigMapName, nil) + }() + + err = k.NamespaceAccessPoliciesDeleteNamespace(test.namespaceToDelete) + assert.NoError(t, err, "failed to delete namespace") + + policies, err := k.GetNamespaceAccessPolicies() + assert.NoError(t, err, "failed to fetch policies") + assert.Equal(t, test.expectedConfig, policies) + }) + } +} diff --git a/api/kubernetes/cli/client.go b/api/kubernetes/cli/client.go index c3e687fd9..9e2794bb1 100644 --- a/api/kubernetes/cli/client.go +++ b/api/kubernetes/cli/client.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "strconv" + "sync" "time" cmap "github.com/orcaman/concurrent-map" @@ -27,8 +28,9 @@ type ( // KubeClient represent a service used to execute Kubernetes operations KubeClient struct { - cli *kubernetes.Clientset + cli kubernetes.Interface instanceID string + lock *sync.Mutex } ) @@ -75,6 +77,7 @@ func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint) (po kubecli := &KubeClient{ cli: cli, instanceID: factory.instanceID, + lock: &sync.Mutex{}, } return kubecli, nil diff --git a/api/portainer.go b/api/portainer.go index 5458fca4b..ae971cee2 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1173,6 +1173,7 @@ type ( SetupUserServiceAccount(userID int, teamIDs []int) error GetServiceAccountBearerToken(userID int) (string, error) StartExecProcess(namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer) error + NamespaceAccessPoliciesDeleteNamespace(namespace string) error GetNamespaceAccessPolicies() (map[string]K8sNamespaceAccessPolicy, error) UpdateNamespaceAccessPolicies(accessPolicies map[string]K8sNamespaceAccessPolicy) error DeleteRegistrySecret(registry *Registry, namespace string) error