mirror of
https://github.com/portainer/portainer.git
synced 2025-07-24 15:59:41 +02:00
fix(kubernetes): events api to call the backend [R8S-243] (#563)
This commit is contained in:
parent
32ef208278
commit
07dfd981a2
26 changed files with 750 additions and 217 deletions
|
@ -30,8 +30,8 @@ func (handler *Handler) prepareKubeClient(r *http.Request) (*cli.KubeClient, *ht
|
|||
log.Error().Err(err).Str("context", "prepareKubeClient").Msg("Unable to get a privileged Kubernetes client for the user.")
|
||||
return nil, httperror.InternalServerError("Unable to get a privileged Kubernetes client for the user.", err)
|
||||
}
|
||||
pcli.IsKubeAdmin = cli.IsKubeAdmin
|
||||
pcli.NonAdminNamespaces = cli.NonAdminNamespaces
|
||||
pcli.SetIsKubeAdmin(cli.GetIsKubeAdmin())
|
||||
pcli.SetClientNonAdminNamespaces(cli.GetClientNonAdminNamespaces())
|
||||
|
||||
return pcli, nil
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func (handler *Handler) getAllKubernetesClusterRoleBindings(w http.ResponseWrite
|
|||
return httperror.Forbidden("User is not authorized to fetch cluster role bindings from the Kubernetes cluster.", httpErr)
|
||||
}
|
||||
|
||||
if !cli.IsKubeAdmin {
|
||||
if !cli.GetIsKubeAdmin() {
|
||||
log.Error().Str("context", "getAllKubernetesClusterRoleBindings").Msg("user is not authorized to fetch cluster role bindings from the Kubernetes cluster.")
|
||||
return httperror.Forbidden("User is not authorized to fetch cluster role bindings from the Kubernetes cluster.", nil)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func (handler *Handler) getAllKubernetesClusterRoles(w http.ResponseWriter, r *h
|
|||
return httperror.Forbidden("User is not authorized to fetch cluster roles from the Kubernetes cluster.", httpErr)
|
||||
}
|
||||
|
||||
if !cli.IsKubeAdmin {
|
||||
if !cli.GetIsKubeAdmin() {
|
||||
log.Error().Str("context", "getAllKubernetesClusterRoles").Msg("user is not authorized to fetch cluster roles from the Kubernetes cluster.")
|
||||
return httperror.Forbidden("User is not authorized to fetch cluster roles from the Kubernetes cluster.", nil)
|
||||
}
|
||||
|
|
102
api/http/handler/kubernetes/event.go
Normal file
102
api/http/handler/kubernetes/event.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
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"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
// @id getKubernetesEventsForNamespace
|
||||
// @summary Gets kubernetes events for namespace
|
||||
// @description Get events by optional query param resourceId for a given namespace.
|
||||
// @description **Access policy**: Authenticated user.
|
||||
// @tags kubernetes
|
||||
// @security ApiKeyAuth || jwt
|
||||
// @produce json
|
||||
// @param id path int true "Environment identifier"
|
||||
// @param namespace path string true "The namespace name the events are associated to"
|
||||
// @param resourceId query string false "The resource id of the involved kubernetes object" example:"e5b021b6-4bce-4c06-bd3b-6cca906797aa"
|
||||
// @success 200 {object} models.Event[] "Success"
|
||||
// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria."
|
||||
// @failure 401 "Unauthorized access - the user is not authenticated or does not have the necessary permissions. Ensure that you have provided a valid API key or JWT token, and that you have the required permissions."
|
||||
// @failure 403 "Permission denied - the user is authenticated but does not have the necessary permissions to access the requested resource or perform the specified operation. Check your user roles and permissions."
|
||||
// @failure 500 "Server error occurred while attempting to retrieve the events within the specified namespace."
|
||||
// @router /kubernetes/{id}/namespaces/{namespace}/events [get]
|
||||
func (handler *Handler) getKubernetesEventsForNamespace(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
namespace, err := request.RetrieveRouteVariableValue(r, "namespace")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("context", "getKubernetesEvents").Str("namespace", namespace).Msg("Unable to retrieve namespace identifier route variable")
|
||||
return httperror.BadRequest("Unable to retrieve namespace identifier route variable", err)
|
||||
}
|
||||
|
||||
resourceId, err := request.RetrieveQueryParameter(r, "resourceId", true)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve resourceId query parameter")
|
||||
return httperror.BadRequest("Unable to retrieve resourceId query parameter", err)
|
||||
}
|
||||
|
||||
cli, httpErr := handler.getProxyKubeClient(r)
|
||||
if httpErr != nil {
|
||||
log.Error().Err(httpErr).Str("context", "getKubernetesEvents").Str("resourceId", resourceId).Msg("Unable to get a Kubernetes client for the user")
|
||||
return httperror.InternalServerError("Unable to get a Kubernetes client for the user", httpErr)
|
||||
}
|
||||
|
||||
events, err := cli.GetEvents(namespace, resourceId)
|
||||
if err != nil {
|
||||
if k8serrors.IsUnauthorized(err) || k8serrors.IsForbidden(err) {
|
||||
log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unauthorized access to the Kubernetes API")
|
||||
return httperror.Forbidden("Unauthorized access to the Kubernetes API", err)
|
||||
}
|
||||
|
||||
log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve events")
|
||||
return httperror.InternalServerError("Unable to retrieve events", err)
|
||||
}
|
||||
|
||||
return response.JSON(w, events)
|
||||
}
|
||||
|
||||
// @id getAllKubernetesEvents
|
||||
// @summary Gets kubernetes events
|
||||
// @description Get events by query param resourceId
|
||||
// @description **Access policy**: Authenticated user.
|
||||
// @tags kubernetes
|
||||
// @security ApiKeyAuth || jwt
|
||||
// @produce json
|
||||
// @param id path int true "Environment identifier"
|
||||
// @param resourceId query string false "The resource id of the involved kubernetes object" example:"e5b021b6-4bce-4c06-bd3b-6cca906797aa"
|
||||
// @success 200 {object} models.Event[] "Success"
|
||||
// @failure 400 "Invalid request payload, such as missing required fields or fields not meeting validation criteria."
|
||||
// @failure 401 "Unauthorized access - the user is not authenticated or does not have the necessary permissions. Ensure that you have provided a valid API key or JWT token, and that you have the required permissions."
|
||||
// @failure 403 "Permission denied - the user is authenticated but does not have the necessary permissions to access the requested resource or perform the specified operation. Check your user roles and permissions."
|
||||
// @failure 500 "Server error occurred while attempting to retrieve the events."
|
||||
// @router /kubernetes/{id}/events [get]
|
||||
func (handler *Handler) getAllKubernetesEvents(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
resourceId, err := request.RetrieveQueryParameter(r, "resourceId", true)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve resourceId query parameter")
|
||||
return httperror.BadRequest("Unable to retrieve resourceId query parameter", err)
|
||||
}
|
||||
|
||||
cli, httpErr := handler.getProxyKubeClient(r)
|
||||
if httpErr != nil {
|
||||
log.Error().Err(httpErr).Str("context", "getKubernetesEvents").Str("resourceId", resourceId).Msg("Unable to get a Kubernetes client for the user")
|
||||
return httperror.InternalServerError("Unable to get a Kubernetes client for the user", httpErr)
|
||||
}
|
||||
|
||||
events, err := cli.GetEvents("", resourceId)
|
||||
if err != nil {
|
||||
if k8serrors.IsUnauthorized(err) || k8serrors.IsForbidden(err) {
|
||||
log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unauthorized access to the Kubernetes API")
|
||||
return httperror.Forbidden("Unauthorized access to the Kubernetes API", err)
|
||||
}
|
||||
|
||||
log.Error().Err(err).Str("context", "getKubernetesEvents").Msg("Unable to retrieve events")
|
||||
return httperror.InternalServerError("Unable to retrieve events", err)
|
||||
}
|
||||
|
||||
return response.JSON(w, events)
|
||||
}
|
60
api/http/handler/kubernetes/event_test.go
Normal file
60
api/http/handler/kubernetes/event_test.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/datastore"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
"github.com/portainer/portainer/api/internal/authorization"
|
||||
"github.com/portainer/portainer/api/internal/testhelpers"
|
||||
"github.com/portainer/portainer/api/jwt"
|
||||
"github.com/portainer/portainer/api/kubernetes"
|
||||
kubeClient "github.com/portainer/portainer/api/kubernetes/cli"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Currently this test just tests the HTTP Handler is setup correctly, in the future we should move the ClientFactory to a mock in order
|
||||
// test the logic in event.go
|
||||
func TestGetKubernetesEvents(t *testing.T) {
|
||||
is := assert.New(t)
|
||||
|
||||
_, store := datastore.MustNewTestStore(t, true, true)
|
||||
|
||||
err := store.Endpoint().Create(&portainer.Endpoint{
|
||||
ID: 1,
|
||||
Type: portainer.AgentOnKubernetesEnvironment,
|
||||
},
|
||||
)
|
||||
is.NoError(err, "error creating environment")
|
||||
|
||||
err = store.User().Create(&portainer.User{Username: "admin", Role: portainer.AdministratorRole})
|
||||
is.NoError(err, "error creating a user")
|
||||
|
||||
jwtService, err := jwt.NewService("1h", store)
|
||||
is.NoError(err, "Error initiating jwt service")
|
||||
|
||||
tk, _, _ := jwtService.GenerateToken(&portainer.TokenData{ID: 1, Username: "admin", Role: portainer.AdministratorRole})
|
||||
|
||||
kubeClusterAccessService := kubernetes.NewKubeClusterAccessService("", "", "")
|
||||
|
||||
cli := testhelpers.NewKubernetesClient()
|
||||
factory, _ := kubeClient.NewClientFactory(nil, nil, store, "", "", "")
|
||||
|
||||
authorizationService := authorization.NewService(store)
|
||||
handler := NewHandler(testhelpers.NewTestRequestBouncer(), authorizationService, store, jwtService, kubeClusterAccessService,
|
||||
factory, cli)
|
||||
is.NotNil(handler, "Handler should not fail")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/kubernetes/1/events?resourceId=8", nil)
|
||||
ctx := security.StoreTokenData(req, &portainer.TokenData{ID: 1, Username: "admin", Role: 1})
|
||||
req = req.WithContext(ctx)
|
||||
testhelpers.AddTestSecurityCookie(req, tk)
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
is.Equal(http.StatusOK, rr.Code, "Status should be 200")
|
||||
}
|
|
@ -58,6 +58,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza
|
|||
endpointRouter.Handle("/configmaps/count", httperror.LoggerHandler(h.getAllKubernetesConfigMapsCount)).Methods(http.MethodGet)
|
||||
endpointRouter.Handle("/cron_jobs", httperror.LoggerHandler(h.getAllKubernetesCronJobs)).Methods(http.MethodGet)
|
||||
endpointRouter.Handle("/cron_jobs/delete", httperror.LoggerHandler(h.deleteKubernetesCronJobs)).Methods(http.MethodPost)
|
||||
endpointRouter.Handle("/events", httperror.LoggerHandler(h.getAllKubernetesEvents)).Methods(http.MethodGet)
|
||||
endpointRouter.Handle("/jobs", httperror.LoggerHandler(h.getAllKubernetesJobs)).Methods(http.MethodGet)
|
||||
endpointRouter.Handle("/jobs/delete", httperror.LoggerHandler(h.deleteKubernetesJobs)).Methods(http.MethodPost)
|
||||
endpointRouter.Handle("/cluster_roles", httperror.LoggerHandler(h.getAllKubernetesClusterRoles)).Methods(http.MethodGet)
|
||||
|
@ -110,6 +111,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza
|
|||
// to keep it simple, we've decided to leave it like this.
|
||||
namespaceRouter := endpointRouter.PathPrefix("/namespaces/{namespace}").Subrouter()
|
||||
namespaceRouter.Handle("/configmaps/{configmap}", httperror.LoggerHandler(h.getKubernetesConfigMap)).Methods(http.MethodGet)
|
||||
namespaceRouter.Handle("/events", httperror.LoggerHandler(h.getKubernetesEventsForNamespace)).Methods(http.MethodGet)
|
||||
namespaceRouter.Handle("/system", bouncer.RestrictedAccess(httperror.LoggerHandler(h.namespacesToggleSystem))).Methods(http.MethodPut)
|
||||
namespaceRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.getKubernetesIngressControllersByNamespace)).Methods(http.MethodGet)
|
||||
namespaceRouter.Handle("/ingresscontrollers", httperror.LoggerHandler(h.updateKubernetesIngressControllersByNamespace)).Methods(http.MethodPut)
|
||||
|
@ -133,7 +135,7 @@ func NewHandler(bouncer security.BouncerService, authorizationService *authoriza
|
|||
// getProxyKubeClient gets a kubeclient for the user. It's generally what you want as it retrieves the kubeclient
|
||||
// from the Authorization token of the currently logged in user. The kubeclient that is not from the proxy is actually using
|
||||
// admin permissions. If you're unsure which one to use, use this.
|
||||
func (h *Handler) getProxyKubeClient(r *http.Request) (*cli.KubeClient, *httperror.HandlerError) {
|
||||
func (h *Handler) getProxyKubeClient(r *http.Request) (portainer.KubeClient, *httperror.HandlerError) {
|
||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return nil, httperror.BadRequest(fmt.Sprintf("an error occurred during the getProxyKubeClient operation, the environment identifier route variable is invalid for /api/kubernetes/%d. Error: ", endpointID), err)
|
||||
|
@ -253,7 +255,7 @@ func (handler *Handler) kubeClientMiddleware(next http.Handler) http.Handler {
|
|||
return
|
||||
}
|
||||
serverURL.Scheme = "https"
|
||||
serverURL.Host = "localhost" + handler.KubernetesClientFactory.AddrHTTPS
|
||||
serverURL.Host = "localhost" + handler.KubernetesClientFactory.GetAddrHTTPS()
|
||||
config.Clusters[0].Cluster.Server = serverURL.String()
|
||||
|
||||
yaml, err := cli.GenerateYAML(config)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue