diff --git a/api/http/handler/endpoints/endpoint_update.go b/api/http/handler/endpoints/endpoint_update.go
index 99dfd9571..6788867f1 100644
--- a/api/http/handler/endpoints/endpoint_update.go
+++ b/api/http/handler/endpoints/endpoint_update.go
@@ -151,11 +151,17 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
}
}
+ updateAuthorizations := false
+
if payload.Kubernetes != nil {
+ if payload.Kubernetes.Configuration.RestrictDefaultNamespace !=
+ endpoint.Kubernetes.Configuration.RestrictDefaultNamespace {
+ updateAuthorizations = true
+ }
+
endpoint.Kubernetes = *payload.Kubernetes
}
- updateAuthorizations := false
if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpoint.UserAccessPolicies) {
updateAuthorizations = true
endpoint.UserAccessPolicies = payload.UserAccessPolicies
diff --git a/api/http/proxy/factory/kubernetes/agent_transport.go b/api/http/proxy/factory/kubernetes/agent_transport.go
index ea5af40a1..2973d46c9 100644
--- a/api/http/proxy/factory/kubernetes/agent_transport.go
+++ b/api/http/proxy/factory/kubernetes/agent_transport.go
@@ -34,7 +34,7 @@ func NewAgentTransport(signatureService portainer.DigitalSignatureService, tlsCo
// RoundTrip is the implementation of the the http.RoundTripper interface
func (transport *agentTransport) RoundTrip(request *http.Request) (*http.Response, error) {
- token, err := getRoundTripToken(request, transport.tokenManager)
+ token, err := transport.getRoundTripToken(request, transport.tokenManager)
if err != nil {
return nil, err
}
diff --git a/api/http/proxy/factory/kubernetes/edge_transport.go b/api/http/proxy/factory/kubernetes/edge_transport.go
index 22178fa05..042982a4d 100644
--- a/api/http/proxy/factory/kubernetes/edge_transport.go
+++ b/api/http/proxy/factory/kubernetes/edge_transport.go
@@ -31,7 +31,7 @@ func NewEdgeTransport(reverseTunnelService portainer.ReverseTunnelService, endpo
// RoundTrip is the implementation of the the http.RoundTripper interface
func (transport *edgeTransport) RoundTrip(request *http.Request) (*http.Response, error) {
- token, err := getRoundTripToken(request, transport.tokenManager)
+ token, err := transport.getRoundTripToken(request, transport.tokenManager)
if err != nil {
return nil, err
}
diff --git a/api/http/proxy/factory/kubernetes/token.go b/api/http/proxy/factory/kubernetes/token.go
index bfcc145d8..909df1811 100644
--- a/api/http/proxy/factory/kubernetes/token.go
+++ b/api/http/proxy/factory/kubernetes/token.go
@@ -45,7 +45,7 @@ func (manager *tokenManager) getAdminServiceAccountToken() string {
return manager.adminToken
}
-func (manager *tokenManager) getUserServiceAccountToken(userID int) (string, error) {
+func (manager *tokenManager) getUserServiceAccountToken(userID int, endpointID portainer.EndpointID) (string, error) {
manager.mutex.Lock()
defer manager.mutex.Unlock()
@@ -61,7 +61,13 @@ func (manager *tokenManager) getUserServiceAccountToken(userID int) (string, err
teamIds = append(teamIds, int(membership.TeamID))
}
- err = manager.kubecli.SetupUserServiceAccount(userID, teamIds)
+ endpoint, err := manager.dataStore.Endpoint().Endpoint(endpointID)
+ if err != nil {
+ return "", err
+ }
+
+ restrictDefaultNamespace := endpoint.Kubernetes.Configuration.RestrictDefaultNamespace
+ err = manager.kubecli.SetupUserServiceAccount(userID, teamIds, restrictDefaultNamespace)
if err != nil {
return "", err
}
diff --git a/api/http/proxy/factory/kubernetes/transport.go b/api/http/proxy/factory/kubernetes/transport.go
index b71ea906c..d87dea133 100644
--- a/api/http/proxy/factory/kubernetes/transport.go
+++ b/api/http/proxy/factory/kubernetes/transport.go
@@ -87,7 +87,7 @@ func (transport *baseTransport) executeKubernetesRequest(request *http.Request)
// #region ROUND TRIP
func (transport *baseTransport) prepareRoundTrip(request *http.Request) (string, error) {
- token, err := getRoundTripToken(request, transport.tokenManager)
+ token, err := transport.getRoundTripToken(request, transport.tokenManager)
if err != nil {
return "", err
}
@@ -102,7 +102,7 @@ func (transport *baseTransport) RoundTrip(request *http.Request) (*http.Response
return transport.proxyKubernetesRequest(request)
}
-func getRoundTripToken(request *http.Request, tokenManager *tokenManager) (string, error) {
+func (transport *baseTransport) getRoundTripToken(request *http.Request, tokenManager *tokenManager) (string, error) {
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return "", err
@@ -112,7 +112,7 @@ func getRoundTripToken(request *http.Request, tokenManager *tokenManager) (strin
if tokenData.Role == portainer.AdministratorRole {
token = tokenManager.getAdminServiceAccountToken()
} else {
- token, err = tokenManager.getUserServiceAccountToken(int(tokenData.ID))
+ token, err = tokenManager.getUserServiceAccountToken(int(tokenData.ID), transport.endpoint.ID)
if err != nil {
log.Printf("Failed retrieving service account token: %v", err)
return "", err
diff --git a/api/kubernetes/cli/access.go b/api/kubernetes/cli/access.go
index e8725292f..1e0712e0e 100644
--- a/api/kubernetes/cli/access.go
+++ b/api/kubernetes/cli/access.go
@@ -9,10 +9,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
-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()
@@ -48,18 +44,8 @@ func (kcl *KubeClient) GetNamespaceAccessPolicies() (map[string]portainer.K8sNam
return policies, nil
}
-func (kcl *KubeClient) setupNamespaceAccesses(userID int, teamIDs []int, serviceAccountName string) error {
- configMap, err := kcl.cli.CoreV1().ConfigMaps(portainerNamespace).Get(portainerConfigMapName, metav1.GetOptions{})
- if k8serrors.IsNotFound(err) {
- return nil
- } else if err != nil {
- return err
- }
-
- accessData := configMap.Data[portainerConfigMapAccessPoliciesKey]
-
- var accessPolicies namespaceAccessPolicies
- err = json.Unmarshal([]byte(accessData), &accessPolicies)
+func (kcl *KubeClient) setupNamespaceAccesses(userID int, teamIDs []int, serviceAccountName string, restrictDefaultNamespace bool) error {
+ accessPolicies, err := kcl.GetNamespaceAccessPolicies()
if err != nil {
return err
}
@@ -70,20 +56,16 @@ func (kcl *KubeClient) setupNamespaceAccesses(userID int, teamIDs []int, service
}
for _, namespace := range namespaces.Items {
- if namespace.Name == defaultNamespace {
- continue
- }
-
- policies, ok := accessPolicies[namespace.Name]
- if !ok {
- err = kcl.removeNamespaceAccessForServiceAccount(serviceAccountName, namespace.Name)
+ if namespace.Name == defaultNamespace && !restrictDefaultNamespace {
+ err = kcl.ensureNamespaceAccessForServiceAccount(serviceAccountName, defaultNamespace)
if err != nil {
return err
}
continue
}
- if !hasUserAccessToNamespace(userID, teamIDs, policies) {
+ policies, ok := accessPolicies[namespace.Name]
+ if !ok || !hasUserAccessToNamespace(userID, teamIDs, policies) {
err = kcl.removeNamespaceAccessForServiceAccount(serviceAccountName, namespace.Name)
if err != nil {
return err
diff --git a/api/kubernetes/cli/service_account.go b/api/kubernetes/cli/service_account.go
index d8abc6f0f..c09d16141 100644
--- a/api/kubernetes/cli/service_account.go
+++ b/api/kubernetes/cli/service_account.go
@@ -17,7 +17,7 @@ func (kcl *KubeClient) GetServiceAccountBearerToken(userID int) (string, error)
// SetupUserServiceAccount will make sure that all the required resources are created inside the Kubernetes
// cluster before creating a ServiceAccount and a ServiceAccountToken for the specified Portainer user.
//It will also create required default RoleBinding and ClusterRoleBinding rules.
-func (kcl *KubeClient) SetupUserServiceAccount(userID int, teamIDs []int) error {
+func (kcl *KubeClient) SetupUserServiceAccount(userID int, teamIDs []int, restrictDefaultNamespace bool) error {
serviceAccountName := userServiceAccountName(userID, kcl.instanceID)
err := kcl.ensureRequiredResourcesExist()
@@ -25,20 +25,7 @@ func (kcl *KubeClient) SetupUserServiceAccount(userID int, teamIDs []int) error
return err
}
- err = kcl.ensureServiceAccountForUserExists(serviceAccountName)
- if err != nil {
- return err
- }
-
- return kcl.setupNamespaceAccesses(userID, teamIDs, serviceAccountName)
-}
-
-func (kcl *KubeClient) ensureRequiredResourcesExist() error {
- return kcl.createPortainerUserClusterRole()
-}
-
-func (kcl *KubeClient) ensureServiceAccountForUserExists(serviceAccountName string) error {
- err := kcl.createUserServiceAccount(portainerNamespace, serviceAccountName)
+ err = kcl.createUserServiceAccount(portainerNamespace, serviceAccountName)
if err != nil {
return err
}
@@ -53,7 +40,11 @@ func (kcl *KubeClient) ensureServiceAccountForUserExists(serviceAccountName stri
return err
}
- return kcl.ensureNamespaceAccessForServiceAccount(serviceAccountName, defaultNamespace)
+ return kcl.setupNamespaceAccesses(userID, teamIDs, serviceAccountName, restrictDefaultNamespace)
+}
+
+func (kcl *KubeClient) ensureRequiredResourcesExist() error {
+ return kcl.createPortainerUserClusterRole()
}
func (kcl *KubeClient) createUserServiceAccount(namespace, serviceAccountName string) error {
diff --git a/api/portainer.go b/api/portainer.go
index 78201f4af..ba027d5c7 100644
--- a/api/portainer.go
+++ b/api/portainer.go
@@ -414,10 +414,11 @@ type (
// KubernetesConfiguration represents the configuration of a Kubernetes endpoint
KubernetesConfiguration struct {
- UseLoadBalancer bool `json:"UseLoadBalancer"`
- UseServerMetrics bool `json:"UseServerMetrics"`
- StorageClasses []KubernetesStorageClassConfig `json:"StorageClasses"`
- IngressClasses []KubernetesIngressClassConfig `json:"IngressClasses"`
+ UseLoadBalancer bool `json:"UseLoadBalancer"`
+ UseServerMetrics bool `json:"UseServerMetrics"`
+ StorageClasses []KubernetesStorageClassConfig `json:"StorageClasses"`
+ IngressClasses []KubernetesIngressClassConfig `json:"IngressClasses"`
+ RestrictDefaultNamespace bool `json:"RestrictDefaultNamespace"`
}
// KubernetesStorageClassConfig represents a Kubernetes Storage Class configuration
@@ -1170,7 +1171,7 @@ type (
// KubeClient represents a service used to query a Kubernetes environment
KubeClient interface {
- SetupUserServiceAccount(userID int, teamIDs []int) error
+ SetupUserServiceAccount(userID int, teamIDs []int, restrictDefaultNamespace bool) error
GetServiceAccountBearerToken(userID int) (string, error)
StartExecProcess(namespace, podName, containerName string, command []string, stdin io.Reader, stdout io.Writer) error
NamespaceAccessPoliciesDeleteNamespace(namespace string) error
diff --git a/api/swagger.yaml b/api/swagger.yaml
index 64f2674bc..77513bf0a 100644
--- a/api/swagger.yaml
+++ b/api/swagger.yaml
@@ -1003,6 +1003,8 @@ definitions:
type: boolean
UseServerMetrics:
type: boolean
+ RestrictDefaultNamespace:
+ type: boolean
type: object
portainer.KubernetesData:
properties:
diff --git a/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatable.js b/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatable.js
index 8023b8518..520b50a8a 100644
--- a/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatable.js
+++ b/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatable.js
@@ -10,5 +10,6 @@ angular.module('portainer.kubernetes').component('kubernetesResourcePoolsDatatab
reverseOrder: '<',
removeAction: '<',
refreshCallback: '<',
+ endpoint: '<',
},
});
diff --git a/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js b/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js
index b3181bfe9..c81af255d 100644
--- a/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js
+++ b/app/kubernetes/components/datatables/resource-pools-datatable/resourcePoolsDatatableController.js
@@ -18,7 +18,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) {
diff --git a/app/kubernetes/views/configure/configure.html b/app/kubernetes/views/configure/configure.html
index 98f86b0d1..7927aecf5 100644
--- a/app/kubernetes/views/configure/configure.html
+++ b/app/kubernetes/views/configure/configure.html
@@ -145,11 +145,7 @@
-
-
-
- This feature is available in Portainer Business Edition.
-
+
diff --git a/app/kubernetes/views/configure/configureController.js b/app/kubernetes/views/configure/configureController.js
index 10184786c..8396caa47 100644
--- a/app/kubernetes/views/configure/configureController.js
+++ b/app/kubernetes/views/configure/configureController.js
@@ -107,6 +107,7 @@ class KubernetesConfigureController {
endpoint.Kubernetes.Configuration.UseLoadBalancer = this.formValues.UseLoadBalancer;
endpoint.Kubernetes.Configuration.UseServerMetrics = this.formValues.UseServerMetrics;
endpoint.Kubernetes.Configuration.IngressClasses = ingressClasses;
+ endpoint.Kubernetes.Configuration.RestrictDefaultNamespace = this.formValues.RestrictDefaultNamespace;
}
transformFormValues() {
@@ -259,6 +260,7 @@ class KubernetesConfigureController {
UseLoadBalancer: false,
UseServerMetrics: false,
IngressClasses: [],
+ RestrictDefaultNamespace: false,
};
try {
@@ -281,6 +283,7 @@ class KubernetesConfigureController {
this.formValues.UseLoadBalancer = this.endpoint.Kubernetes.Configuration.UseLoadBalancer;
this.formValues.UseServerMetrics = this.endpoint.Kubernetes.Configuration.UseServerMetrics;
+ this.formValues.RestrictDefaultNamespace = this.endpoint.Kubernetes.Configuration.RestrictDefaultNamespace;
this.formValues.IngressClasses = _.map(this.endpoint.Kubernetes.Configuration.IngressClasses, (ic) => {
ic.IsNew = false;
ic.NeedsDeletion = false;
diff --git a/app/kubernetes/views/resource-pools/resourcePools.html b/app/kubernetes/views/resource-pools/resourcePools.html
index c1ec578e4..71eee9399 100644
--- a/app/kubernetes/views/resource-pools/resourcePools.html
+++ b/app/kubernetes/views/resource-pools/resourcePools.html
@@ -13,6 +13,7 @@
order-by="Namespace.Name"
remove-action="ctrl.removeAction"
refresh-callback="ctrl.getResourcePools"
+ endpoint="ctrl.endpoint"
>
diff --git a/app/kubernetes/views/resource-pools/resourcePools.js b/app/kubernetes/views/resource-pools/resourcePools.js
index 030e1e6eb..bf999a576 100644
--- a/app/kubernetes/views/resource-pools/resourcePools.js
+++ b/app/kubernetes/views/resource-pools/resourcePools.js
@@ -2,4 +2,7 @@ angular.module('portainer.kubernetes').component('kubernetesResourcePoolsView',
templateUrl: './resourcePools.html',
controller: 'KubernetesResourcePoolsController',
controllerAs: 'ctrl',
+ bindings: {
+ endpoint: '<',
+ },
});
diff --git a/package.json b/package.json
index aa00f0908..56c9858c6 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
"dev:client:prod": "grunt clean:client && webpack-dev-server --config=./webpack/webpack.production.js",
"dev:nodl": "grunt clean:server && grunt clean:client && grunt build:server && grunt copy:assets && grunt start:client",
"start:toolkit": "grunt start:toolkit",
- "build:server:offline": "cd ./api/cmd/portainer && CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s' && mv -f portainer ../../../dist/portainer",
+ "build:server:offline": "cd ./api/cmd/portainer && CGO_ENABLED=0 go build --installsuffix cgo --ldflags '-s' && mv -f portainer ../../../dist/portainer",
"clean:all": "grunt clean:all",
"format": "prettier --loglevel warn --write \"**/*.{js,css,html}\"",
"lint": "yarn lint:client; yarn lint:server",