diff --git a/api/go.sum b/api/go.sum index d7b7db557..15917ef26 100644 --- a/api/go.sum +++ b/api/go.sum @@ -405,10 +405,13 @@ k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb h1:ZUNsbuPdXWrj0rZziRfCWc k8s.io/apimachinery v0.0.0-20191028221656-72ed19daf4bb/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= k8s.io/apimachinery v0.17.2 h1:hwDQQFbdRlpnnsR64Asdi55GyCaIP/3WQpMmbNBeWr4= k8s.io/apimachinery v0.17.2/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= +k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= +k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= k8s.io/client-go v0.0.0-20191114101535-6c5935290e33 h1:07mhG/2oEoo3N+sHVOo0L9PJ/qvbk3N5n2dj8IWefnQ= k8s.io/client-go v0.0.0-20191114101535-6c5935290e33/go.mod h1:4L/zQOBkEf4pArQJ+CMk1/5xjA30B5oyWv+Bzb44DOw= k8s.io/client-go v0.17.2 h1:ndIfkfXEGrNhLIgkr0+qhRguSD3u6DCmonepn1O6NYc= k8s.io/client-go v0.17.2/go.mod h1:QAzRgsa0C2xl4/eVpeVAZMvikCn8Nm81yqVx3Kk9XYI= +k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go index 19188338c..1c235c5cb 100644 --- a/api/http/handler/endpoints/endpoint_update.go +++ b/api/http/handler/endpoints/endpoint_update.go @@ -122,6 +122,7 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * } } + oldRestrictDefaultNamespace := endpoint.Kubernetes.Configuration.RestrictDefaultNamespace if payload.Kubernetes != nil { endpoint.Kubernetes = *payload.Kubernetes } @@ -269,5 +270,32 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) * } } + if payload.Kubernetes != nil && payload.Kubernetes.Configuration.RestrictDefaultNamespace != oldRestrictDefaultNamespace { + users, err := handler.DataStore.User().Users() + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find users inside the database", err} + } + cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint, handler.DataStore) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create Kubernetes client", err} + } + for _, user := range users { + memberships, err := handler.DataStore.TeamMembership().TeamMembershipsByUserID(user.ID) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find memberships inside the database", err} + } + + teamIds := make([]int, 0) + for _, membership := range memberships { + teamIds = append(teamIds, int(membership.TeamID)) + } + + err = cli.SetupUserServiceAccount(int(user.ID), teamIds) + if err != nil { + return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update namespaces accesses", err} + } + } + } + return response.JSON(w, endpoint) } diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go index 8234e3553..972397632 100644 --- a/api/http/handler/endpoints/handler.go +++ b/api/http/handler/endpoints/handler.go @@ -9,6 +9,7 @@ import ( "github.com/portainer/portainer/api/http/proxy" "github.com/portainer/portainer/api/http/security" "github.com/portainer/portainer/api/internal/authorization" + "github.com/portainer/portainer/api/kubernetes/cli" ) func hideFields(endpoint *portainer.Endpoint) { @@ -21,13 +22,14 @@ func hideFields(endpoint *portainer.Endpoint) { // Handler is the HTTP handler used to handle endpoint operations. type Handler struct { *mux.Router - requestBouncer *security.RequestBouncer - AuthorizationService *authorization.Service - DataStore portainer.DataStore - FileService portainer.FileService - ProxyManager *proxy.Manager - ReverseTunnelService portainer.ReverseTunnelService - SnapshotService portainer.SnapshotService + requestBouncer *security.RequestBouncer + AuthorizationService *authorization.Service + DataStore portainer.DataStore + FileService portainer.FileService + ProxyManager *proxy.Manager + ReverseTunnelService portainer.ReverseTunnelService + SnapshotService portainer.SnapshotService + KubernetesClientFactory *cli.ClientFactory } // NewHandler creates a handler to manage endpoint operations. diff --git a/api/http/handler/websocket/pod.go b/api/http/handler/websocket/pod.go index 103b2b60f..b8d3fb424 100644 --- a/api/http/handler/websocket/pod.go +++ b/api/http/handler/websocket/pod.go @@ -98,7 +98,7 @@ func (handler *Handler) websocketPodExec(w http.ResponseWriter, r *http.Request) go streamFromWebsocketToWriter(websocketConn, stdinWriter, errorChan) go streamFromReaderToWebsocket(websocketConn, stdoutReader, errorChan) - cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint) + cli, err := handler.KubernetesClientFactory.GetKubeClient(endpoint, handler.DataStore) if err != nil { return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create Kubernetes client", err} } diff --git a/api/http/proxy/factory/kubernetes.go b/api/http/proxy/factory/kubernetes.go index 2cb09dc62..a318e4c77 100644 --- a/api/http/proxy/factory/kubernetes.go +++ b/api/http/proxy/factory/kubernetes.go @@ -28,7 +28,7 @@ func (factory *ProxyFactory) newKubernetesLocalProxy(endpoint *portainer.Endpoin return nil, err } - kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint) + kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint, factory.dataStore) if err != nil { return nil, err } @@ -59,7 +59,7 @@ func (factory *ProxyFactory) newKubernetesEdgeHTTPProxy(endpoint *portainer.Endp return nil, err } - kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint) + kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint, factory.dataStore) if err != nil { return nil, err } @@ -86,7 +86,7 @@ func (factory *ProxyFactory) newKubernetesAgentHTTPSProxy(endpoint *portainer.En remoteURL.Scheme = "https" - kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint) + kubecli, err := factory.kubernetesClientFactory.GetKubeClient(endpoint, factory.dataStore) if err != nil { return nil, err } diff --git a/api/http/server.go b/api/http/server.go index b7f58690e..557b7889e 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -124,6 +124,7 @@ func (server *Server) Start() error { endpointHandler.SnapshotService = server.SnapshotService endpointHandler.ProxyManager = proxyManager endpointHandler.ReverseTunnelService = server.ReverseTunnelService + endpointHandler.KubernetesClientFactory = server.KubernetesClientFactory var endpointEdgeHandler = endpointedge.NewHandler(requestBouncer) endpointEdgeHandler.DataStore = server.DataStore diff --git a/api/kubernetes.go b/api/kubernetes.go index 33f446ee2..d58e398ec 100644 --- a/api/kubernetes.go +++ b/api/kubernetes.go @@ -9,6 +9,7 @@ func KubernetesDefault() KubernetesData { ResourceOverCommitPercentage: 80, StorageClasses: []KubernetesStorageClassConfig{}, IngressClasses: []KubernetesIngressClassConfig{}, + RestrictDefaultNamespace: false, }, Snapshots: []KubernetesSnapshot{}, } diff --git a/api/kubernetes/cli/access.go b/api/kubernetes/cli/access.go index bd79d3fce..3d266ef25 100644 --- a/api/kubernetes/cli/access.go +++ b/api/kubernetes/cli/access.go @@ -18,6 +18,18 @@ type ( ) func (kcl *KubeClient) setupNamespaceAccesses(userID int, teamIDs []int, serviceAccountName string) error { + endpoint, err := kcl.dataStore.Endpoint().Endpoint(portainer.EndpointID(kcl.endpointID)) + if err != nil { + return err + } + + if endpoint.Kubernetes.Configuration.RestrictDefaultNamespace { + err = kcl.removeNamespaceAccessForServiceAccount(serviceAccountName, defaultNamespace) + if err != nil { + return err + } + } + configMap, err := kcl.cli.CoreV1().ConfigMaps(portainerNamespace).Get(portainerConfigMapName, metav1.GetOptions{}) if k8serrors.IsNotFound(err) { return nil @@ -38,8 +50,23 @@ func (kcl *KubeClient) setupNamespaceAccesses(userID int, teamIDs []int, service return err } + if !endpoint.Kubernetes.Configuration.RestrictDefaultNamespace { + if _, ok := accessPolicies[defaultNamespace]; ok { + delete(accessPolicies, defaultNamespace) + data, err := json.Marshal(accessPolicies) + if err != nil { + return err + } + configMap.Data[portainerConfigMapAccessPoliciesKey] = string(data) + _, err = kcl.cli.CoreV1().ConfigMaps(portainerNamespace).Update(configMap) + if err != nil { + return err + } + } + } + for _, namespace := range namespaces.Items { - if namespace.Name == defaultNamespace { + if namespace.Name == defaultNamespace && !endpoint.Kubernetes.Configuration.RestrictDefaultNamespace { continue } diff --git a/api/kubernetes/cli/client.go b/api/kubernetes/cli/client.go index 707bf3924..5266e3051 100644 --- a/api/kubernetes/cli/client.go +++ b/api/kubernetes/cli/client.go @@ -27,6 +27,8 @@ type ( KubeClient struct { cli *kubernetes.Clientset instanceID string + endpointID portainer.EndpointID + dataStore portainer.DataStore } ) @@ -42,11 +44,11 @@ func NewClientFactory(signatureService portainer.DigitalSignatureService, revers // GetKubeClient checks if an existing client is already registered for the endpoint and returns it if one is found. // If no client is registered, it will create a new client, register it, and returns it. -func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) { +func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint, dataStore portainer.DataStore) (portainer.KubeClient, error) { key := strconv.Itoa(int(endpoint.ID)) client, ok := factory.endpointClients.Get(key) if !ok { - client, err := factory.createKubeClient(endpoint) + client, err := factory.createKubeClient(endpoint, dataStore) if err != nil { return nil, err } @@ -58,7 +60,7 @@ func (factory *ClientFactory) GetKubeClient(endpoint *portainer.Endpoint) (porta return client.(portainer.KubeClient), nil } -func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint) (portainer.KubeClient, error) { +func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint, dataStore portainer.DataStore) (portainer.KubeClient, error) { cli, err := factory.CreateClient(endpoint) if err != nil { return nil, err @@ -67,6 +69,8 @@ func (factory *ClientFactory) createKubeClient(endpoint *portainer.Endpoint) (po kubecli := &KubeClient{ cli: cli, instanceID: factory.instanceID, + endpointID: endpoint.ID, + dataStore: dataStore, } return kubecli, nil diff --git a/api/portainer.go b/api/portainer.go index 3e9f45109..60dc9ecb7 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -343,6 +343,7 @@ type ( ResourceOverCommitPercentage int `json:"ResourceOverCommitPercentage"` StorageClasses []KubernetesStorageClassConfig `json:"StorageClasses"` IngressClasses []KubernetesIngressClassConfig `json:"IngressClasses"` + RestrictDefaultNamespace bool `json:"RestrictDefaultNamespace"` } // KubernetesStorageClassConfig represents a Kubernetes Storage Class configuration diff --git a/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js b/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js index b3181bfe9..31427061f 100644 --- a/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js +++ b/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js @@ -4,7 +4,8 @@ angular.module('portainer.docker').controller('KubernetesResourcePoolsDatatableC 'Authentication', 'KubernetesNamespaceHelper', 'DatatableService', - function ($scope, $controller, Authentication, KubernetesNamespaceHelper, DatatableService) { + 'EndpointProvider', + function ($scope, $controller, Authentication, KubernetesNamespaceHelper, DatatableService, EndpointProvider) { angular.extend(this, $controller('GenericDatatableController', { $scope: $scope })); var ctrl = this; @@ -18,7 +19,11 @@ angular.module('portainer.docker').controller('KubernetesResourcePoolsDatatableC }; this.canManageAccess = function (item) { - return item.Namespace.Name !== 'default' && !this.isSystemNamespace(item); + if (!this.endpoint.Kubernetes.Configuration.RestrictDefaultNamespace) { + return item.Namespace.Name !== 'default' && !this.isSystemNamespace(item); + } else { + return !this.isSystemNamespace(item); + } }; this.disableRemove = function (item) { @@ -41,6 +46,7 @@ angular.module('portainer.docker').controller('KubernetesResourcePoolsDatatableC }; this.$onInit = function () { + this.endpoint = EndpointProvider.currentEndpoint(); this.isAdmin = Authentication.isAdmin(); this.setDefaults(); this.prepareTableFromDataset(); diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html index 78e7b2674..62a3668e5 100644 --- a/app/kubernetes/views/applications/create/createApplication.html +++ b/app/kubernetes/views/applications/create/createApplication.html @@ -78,7 +78,7 @@ Resource pool -
Environment variable name is required.
-This field must consist alphanumeric characters, '-' or '_', start with an alphabetic - character, and end with an alphanumeric character (e.g. 'my-var', or 'MY_VAR123').
-This environment variable is already defined.
-{{ config.SelectedConfiguration.Name }} configuration as environment variables:
-
- {{ key }}{{ $last ? '' : ', ' }}
-
- Path is required.
-This path is already used.
-Path is required.
-This path is already defined.
-Size is required.
-This value must be greater than zero.
-Volume is required.
-This volume is already used.
-- Maximum memory usage (MB) -
-Value must be between {{ ctrl.state.sliders.memory.min }} and {{ ctrl.state.sliders.memory.max }} -
-- Maximum CPU usage -
-{{ ctrl.getNonScalableStorage() }}. You will not be able to scale that application.
- - This feature is currently disabled and must be enabled by an administrator user. -
-- Server metrics features must be enabled in the - endpoint configuration view. -
-| Minimum instances | -Maximum instances | -
- Target CPU usage (%)
- |
-
|
-
-
-
-
-
-
-
- Minimum instances is required. -Minimum instances must be greater than 0. -Minimum instances must be smaller than maximum instances. - |
-
-
-
-
-
-
-
-
- Maximum instances is required. -Maximum instances must be greater than minimum instances. - |
-
-
-
-
-
-
-
-
- Target CPU usage is required. -Target CPU usage must be greater than 0. -Target CPU usage must be smaller than 100. - |
-