port across service accout delete

pull/12297/head
testA113 2024-10-04 14:13:52 +13:00
parent b14438fd99
commit 49e5fb286a
7 changed files with 126 additions and 9 deletions

View File

@ -78,6 +78,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza
endpointRouter.Handle("/secrets", httperror.LoggerHandler(h.GetAllKubernetesSecrets)).Methods(http.MethodGet)
endpointRouter.Handle("/secrets/count", httperror.LoggerHandler(h.getAllKubernetesSecretsCount)).Methods(http.MethodGet)
endpointRouter.Handle("/services/delete", httperror.LoggerHandler(h.deleteKubernetesServices)).Methods(http.MethodPost)
endpointRouter.Handle("/service_accounts/delete", httperror.LoggerHandler(h.deleteKubernetesServiceAccounts)).Methods(http.MethodPost)
endpointRouter.Handle("/rbac_enabled", httperror.LoggerHandler(h.getKubernetesRBACStatus)).Methods(http.MethodGet)
endpointRouter.Handle("/roles", httperror.LoggerHandler(h.getAllKubernetesRoles)).Methods(http.MethodGet)
endpointRouter.Handle("/role_bindings", httperror.LoggerHandler(h.getAllKubernetesRoleBindings)).Methods(http.MethodGet)

View File

@ -3,7 +3,9 @@ package kubernetes
import (
"net/http"
models "github.com/portainer/portainer/api/http/models/kubernetes"
httperror "github.com/portainer/portainer/pkg/libhttp/error"
"github.com/portainer/portainer/pkg/libhttp/request"
"github.com/portainer/portainer/pkg/libhttp/response"
"github.com/rs/zerolog/log"
)
@ -38,3 +40,36 @@ func (handler *Handler) getAllKubernetesServiceAccounts(w http.ResponseWriter, r
return response.JSON(w, serviceAccounts)
}
// @id DeleteServiceAccounts
// @summary Delete the provided service accounts
// @description Delete the provided roles for the given Kubernetes environment
// @description **Access policy**: administrator
// @tags rbac_enabled
// @security ApiKeyAuth
// @security jwt
// @produce text/plain
// @param id path int true "Environment(Endpoint) identifier"
// @param payload body models.K8sServiceAccountDeleteRequests true "Service accounts to delete "
// @success 200 "Success"
// @failure 500 "Server error"
// @router /kubernetes/{id}/service_accounts/delete [POST]
func (handler *Handler) deleteKubernetesServiceAccounts(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload models.K8sServiceAccountDeleteRequests
err := request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return httperror.BadRequest("Invalid request payload", err)
}
cli, handlerErr := handler.getProxyKubeClient(r)
if handlerErr != nil {
return handlerErr
}
err = cli.DeleteServiceAccounts(payload)
if err != nil {
return httperror.InternalServerError("Unable to delete service accounts", err)
}
return nil
}

View File

@ -1,9 +1,30 @@
package kubernetes
import "time"
import (
"errors"
"net/http"
"time"
)
type K8sServiceAccount struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
CreationDate time.Time `json:"creationDate"`
type (
K8sServiceAccount struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
CreationDate time.Time `json:"creationDate"`
}
// K8sServiceAcountDeleteRequests is a mapping of namespace names to a slice of service account names.
K8sServiceAccountDeleteRequests map[string][]string
)
func (r K8sServiceAccountDeleteRequests) Validate(request *http.Request) error {
if len(r) == 0 {
return errors.New("missing deletion request list in payload")
}
for ns := range r {
if len(ns) == 0 {
return errors.New("deletion given with empty namespace")
}
}
return nil
}

View File

@ -0,0 +1,18 @@
package errorlist
import "errors"
// Combine a slice of errors into a single error
// to use this, generate errors by appending to errorList in a loop, then return combine(errorList)
func Combine(errorList []error) error {
if len(errorList) == 0 {
return nil
}
errorMsg := "Multiple errors occurred:"
for _, err := range errorList {
errorMsg += "\n" + err.Error()
}
return errors.New(errorMsg)
}

View File

@ -94,7 +94,7 @@ func parseNamespace(namespace *corev1.Namespace) portainer.K8sNamespaceInfo {
Status: namespace.Status,
CreationDate: namespace.CreationTimestamp.Format(time.RFC3339),
NamespaceOwner: namespace.Labels[namespaceOwnerLabel],
IsSystem: isSystemNamespace(*namespace),
IsSystem: isSystemNamespace(namespace),
IsDefault: namespace.Name == defaultNamespace,
}
}
@ -171,7 +171,7 @@ func (kcl *KubeClient) CreateNamespace(info models.K8sNamespaceDetails) (*corev1
return namespace, nil
}
func isSystemNamespace(namespace corev1.Namespace) bool {
func isSystemNamespace(namespace *corev1.Namespace) bool {
systemLabelValue, hasSystemLabel := namespace.Labels[systemNamespaceLabel]
if hasSystemLabel {
return systemLabelValue == "true"
@ -184,6 +184,15 @@ func isSystemNamespace(namespace corev1.Namespace) bool {
return isSystem
}
func (kcl *KubeClient) isSystemNamespace(namespace string) bool {
ns, err := kcl.cli.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{})
if err != nil {
return false
}
return isSystemNamespace(ns)
}
// ToggleSystemState will set a namespace as a system namespace, or remove this state
// if isSystem is true it will set `systemNamespaceLabel` to "true" and false otherwise
// this will skip if namespace is "default" or if the required state is already set
@ -199,7 +208,7 @@ func (kcl *KubeClient) ToggleSystemState(namespaceName string, isSystem bool) er
return errors.Wrap(err, "failed fetching namespace object")
}
if isSystemNamespace(*namespace) == isSystem {
if isSystemNamespace(namespace) == isSystem {
return nil
}

View File

@ -65,7 +65,7 @@ func Test_ToggleSystemState(t *testing.T) {
ns, err := kcl.cli.CoreV1().Namespaces().Get(context.Background(), nsName, metav1.GetOptions{})
assert.NoError(t, err)
assert.Equal(t, test.isSystem, isSystemNamespace(*ns))
assert.Equal(t, test.isSystem, isSystemNamespace(ns))
})
}
})

View File

@ -2,9 +2,12 @@ package cli
import (
"context"
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/models/kubernetes"
models "github.com/portainer/portainer/api/http/models/kubernetes"
"github.com/portainer/portainer/api/internal/errorlist"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
@ -81,6 +84,36 @@ func (kcl *KubeClient) GetPortainerUserServiceAccount(tokenData *portainer.Token
return serviceAccount, nil
}
// DeleteServices processes a K8sServiceDeleteRequest by deleting each service
// in its given namespace.
func (kcl *KubeClient) DeleteServiceAccounts(reqs kubernetes.K8sServiceAccountDeleteRequests) error {
var errors []error
for namespace := range reqs {
for _, serviceName := range reqs[namespace] {
client := kcl.cli.CoreV1().ServiceAccounts(namespace)
sa, err := client.Get(context.Background(), serviceName, metav1.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
continue
}
return err
}
if kcl.isSystemNamespace(sa.Namespace) {
return fmt.Errorf("cannot delete system service account %q", namespace+"/"+serviceName)
}
if err := client.Delete(context.Background(), serviceName, metav1.DeleteOptions{}); err != nil {
errors = append(errors, err)
}
}
}
return errorlist.Combine(errors)
}
// GetServiceAccountBearerToken returns the ServiceAccountToken associated to the specified user.
func (kcl *KubeClient) GetServiceAccountBearerToken(userID int) (string, error) {
serviceAccountName := UserServiceAccountName(userID, kcl.instanceID)