mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 05:04:33 +08:00
* Removes desktop logins from the ACL and adds them to each Desktop resource as returned to the frontend. This behavior parallels what's currently being done with other similar resources such as Nodes, and fixes a bug where users were able to see all the logins permitted by their roles on every desktop "connect" dropdown, even if the role permitting that login didn't apply to a given desktop. * Extracts common logic out of GetWindowsDesktopLogins (now GetAllowedWindowsDesktopLogins), uses it to create a GetAllowedServerLogins as well, tests both of them together, removing EnumerateServerLogins * reduce unnecessary looping by using maps * fixes flakiness by sorting lists * uses require.ElementsMatch rather than manually sorting lists * consolidates duplicated logic * Consolidates separate server and desktop logic into a single GetAllowedLoginsForResource * fixes tests * fixes import order and test
336 lines
10 KiB
Go
336 lines
10 KiB
Go
/**
|
|
* Copyright 2021 Gravitational, Inc.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package web
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/reversetunnel"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/web/ui"
|
|
)
|
|
|
|
// clusterKubesGet returns a list of kube clusters in a form the UI can present.
|
|
func (h *Handler) clusterKubesGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
clt, err := sctx.GetUserClient(r.Context(), site)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := listResources(clt, r, types.KindKubernetesCluster)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
clusters, err := types.ResourcesWithLabels(resp.Resources).AsKubeClusters()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
accessChecker, err := sctx.GetUserAccessChecker()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return listResourcesGetResponse{
|
|
Items: ui.MakeKubeClusters(clusters, accessChecker.Roles()),
|
|
StartKey: resp.NextKey,
|
|
TotalCount: resp.TotalCount,
|
|
}, nil
|
|
}
|
|
|
|
// clusterKubePodsGet returns a list of Kubernetes Pods in a form the
|
|
// UI can present.
|
|
func (h *Handler) clusterKubePodsGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
clt, err := sctx.NewKubernetesServiceClient(r.Context(), h.cfg.ProxyWebAddr.Addr)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := listKubeResources(r.Context(), clt, r.URL.Query(), site.GetName(), types.KindKubePod)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return listResourcesGetResponse{
|
|
Items: ui.MakeKubeResources(resp.Resources, r.URL.Query().Get("kubeCluster")),
|
|
StartKey: resp.NextKey,
|
|
TotalCount: int(resp.TotalCount),
|
|
}, nil
|
|
}
|
|
|
|
// clusterDatabasesGet returns a list of db servers in a form the UI can present.
|
|
func (h *Handler) clusterDatabasesGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
clt, err := sctx.GetUserClient(r.Context(), site)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := listResources(clt, r, types.KindDatabaseServer)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
servers, err := types.ResourcesWithLabels(resp.Resources).AsDatabaseServers()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Make a list of all proxied databases.
|
|
var databases []types.Database
|
|
for _, server := range servers {
|
|
databases = append(databases, server.GetDatabase())
|
|
}
|
|
|
|
accessChecker, err := sctx.GetUserAccessChecker()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
dbNames, dbUsers, err := getDatabaseUsersAndNames(accessChecker)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return listResourcesGetResponse{
|
|
Items: ui.MakeDatabases(databases, dbUsers, dbNames),
|
|
StartKey: resp.NextKey,
|
|
TotalCount: resp.TotalCount,
|
|
}, nil
|
|
}
|
|
|
|
// clusterDatabaseGet returns a list of db servers in a form the UI can present.
|
|
func (h *Handler) clusterDatabaseGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
databaseName := p.ByName("database")
|
|
if databaseName == "" {
|
|
return nil, trace.BadParameter("database name is required")
|
|
}
|
|
|
|
clt, err := sctx.GetUserClient(r.Context(), site)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
database, err := fetchDatabaseWithName(r.Context(), clt, r, databaseName)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
accessChecker, err := sctx.GetUserAccessChecker()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
dbNames, dbUsers, err := getDatabaseUsersAndNames(accessChecker)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return ui.MakeDatabase(database, dbUsers, dbNames), nil
|
|
}
|
|
|
|
// clusterDatabaseServicesList returns a list of DatabaseServices (database agents) in a form the UI can present.
|
|
func (h *Handler) clusterDatabaseServicesList(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
clt, err := ctx.GetUserClient(r.Context(), site)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := listResources(clt, r, types.KindDatabaseService)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
servers, err := types.ResourcesWithLabels(resp.Resources).AsDatabaseServices()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return listResourcesGetResponse{
|
|
Items: ui.MakeDatabaseServices(servers),
|
|
StartKey: resp.NextKey,
|
|
TotalCount: resp.TotalCount,
|
|
}, nil
|
|
}
|
|
|
|
// clusterDesktopsGet returns a list of desktops in a form the UI can present.
|
|
func (h *Handler) clusterDesktopsGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
clt, err := sctx.GetUserClient(r.Context(), site)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := listResources(clt, r, types.KindWindowsDesktop)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
windowsDesktops, err := types.ResourcesWithLabels(resp.Resources).AsWindowsDesktops()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
accessChecker, err := sctx.GetUserAccessChecker()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
uiDesktops, err := ui.MakeDesktops(windowsDesktops, accessChecker.Roles())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return listResourcesGetResponse{
|
|
Items: uiDesktops,
|
|
StartKey: resp.NextKey,
|
|
TotalCount: resp.TotalCount,
|
|
}, nil
|
|
}
|
|
|
|
// clusterDesktopServicesGet returns a list of desktop services in a form the UI can present.
|
|
func (h *Handler) clusterDesktopServicesGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
// Get a client to the Auth Server with the logged in user's identity. The
|
|
// identity of the logged in user is used to fetch the list of desktop services.
|
|
clt, err := sctx.GetUserClient(r.Context(), site)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := listResources(clt, r, types.KindWindowsDesktopService)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
desktopServices, err := types.ResourcesWithLabels(resp.Resources).AsWindowsDesktopServices()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return listResourcesGetResponse{
|
|
Items: ui.MakeDesktopServices(desktopServices),
|
|
StartKey: resp.NextKey,
|
|
TotalCount: resp.TotalCount,
|
|
}, nil
|
|
}
|
|
|
|
// getDesktopHandle returns a desktop.
|
|
func (h *Handler) getDesktopHandle(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
clt, err := sctx.GetUserClient(r.Context(), site)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
desktopName := p.ByName("desktopName")
|
|
|
|
windowsDesktops, err := clt.GetWindowsDesktops(r.Context(),
|
|
types.WindowsDesktopFilter{Name: desktopName})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if len(windowsDesktops) == 0 {
|
|
return nil, trace.NotFound("expected at least 1 desktop, got 0")
|
|
}
|
|
|
|
accessChecker, err := sctx.GetUserAccessChecker()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// windowsDesktops may contain the same desktop multiple times
|
|
// if multiple Windows Desktop Services are in use. We only need
|
|
// to see the desktop once in the UI, so just take the first one.
|
|
uiDesktop, err := ui.MakeDesktop(windowsDesktops[0], accessChecker.Roles())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return uiDesktop, nil
|
|
}
|
|
|
|
// desktopIsActive checks if a desktop has an active session and returns a desktopIsActive.
|
|
//
|
|
// GET /v1/webapi/sites/:site/desktops/:desktopName/active
|
|
//
|
|
// Response body:
|
|
//
|
|
// {"active": bool}
|
|
func (h *Handler) desktopIsActive(w http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
|
|
desktopName := p.ByName("desktopName")
|
|
trackers, err := h.auth.proxyClient.GetActiveSessionTrackersWithFilter(r.Context(), &types.SessionTrackerFilter{
|
|
Kind: string(types.WindowsDesktopSessionKind),
|
|
State: &types.NullableSessionState{
|
|
State: types.SessionState_SessionStateRunning,
|
|
},
|
|
DesktopName: desktopName,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
clt, err := sctx.GetUserClient(r.Context(), site)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
for _, tracker := range trackers {
|
|
// clt is an auth.ClientI with the role of the user, so
|
|
// clt.GetWindowsDesktops() can be used to confirm that
|
|
// the user has access to the requested desktop.
|
|
desktops, err := clt.GetWindowsDesktops(r.Context(),
|
|
types.WindowsDesktopFilter{Name: tracker.GetDesktopName()})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if len(desktops) == 0 {
|
|
// There are no active sessions for this desktop
|
|
// or the user doesn't have access to it
|
|
break
|
|
} else {
|
|
return desktopIsActive{true}, nil
|
|
}
|
|
}
|
|
|
|
return desktopIsActive{false}, nil
|
|
}
|
|
|
|
func getDatabaseUsersAndNames(accessChecker services.AccessChecker) (dbNames []string, dbUsers []string, err error) {
|
|
dbNames, dbUsers, err = accessChecker.CheckDatabaseNamesAndUsers(0, true /* force ttl override*/)
|
|
if err != nil {
|
|
// if NotFound error:
|
|
// This user cannot request database access, has no assigned database names or users
|
|
//
|
|
// Every other error should be reported upstream.
|
|
if !trace.IsNotFound(err) {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
|
|
// We proceed with an empty list of DBUsers and DBNames
|
|
dbUsers = []string{}
|
|
dbNames = []string{}
|
|
}
|
|
|
|
return dbNames, dbUsers, nil
|
|
}
|
|
|
|
type desktopIsActive struct {
|
|
Active bool `json:"active"`
|
|
}
|