Skip to content

Commit

Permalink
Create db/kube handler and check cloud feature for web billing access (
Browse files Browse the repository at this point in the history
…#6754)

* Create GET db and kube list web handlers (#6672)
* Check cloud feature before setting billing access for web (#6537)
 - Init web handler with auth server feature flags on proxy init
 - Retrieve auth server features by calling Ping when connecting 
  to auth svc which contains the server feature flags in the response
  • Loading branch information
kimlisa authored May 6, 2021
1 parent f0a5923 commit 8e677f2
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 38 deletions.
4 changes: 3 additions & 1 deletion lib/service/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ func (process *TeleportProcess) reconnectToAuthService(role teleport.Role) (*Con
// if connected and client is present, make sure the connector's
// client works, by using call that should succeed at all times
if connector.Client != nil {
_, err = connector.Client.GetNamespace(defaults.Namespace)
pingResponse, err := connector.Client.Ping(process.ExitContext())
if err == nil {
process.setClusterFeatures(pingResponse.GetServerFeatures())
process.log.Infof("%v: features loaded from auth server: %+v", role, pingResponse.GetServerFeatures())
return connector, nil
}
process.log.Debugf("Connected client %v failed to execute test call: %v. Node or proxy credentials are out of sync.", role, err)
Expand Down
52 changes: 37 additions & 15 deletions lib/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
"github.com/gravitational/trace"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth"
Expand Down Expand Up @@ -294,6 +295,9 @@ type TeleportProcess struct {
// appDependCh is used by application service in single process mode to block
// until auth and reverse tunnel servers are ready.
appDependCh chan Event

// clusterFeatures contain flags for supported and unsupported features.
clusterFeatures proto.Features
}

type keyPairKey struct {
Expand Down Expand Up @@ -356,6 +360,22 @@ func (process *TeleportProcess) addConnector(connector *Connector) {
process.connectors[connector.ClientIdentity.ID.Role] = connector
}

func (process *TeleportProcess) setClusterFeatures(features *proto.Features) {
process.Lock()
defer process.Unlock()

if features != nil {
process.clusterFeatures = *features
}
}

func (process *TeleportProcess) getClusterFeatures() proto.Features {
process.Lock()
defer process.Unlock()

return process.clusterFeatures
}

// GetIdentity returns the process identity (credentials to the auth server) for a given
// teleport Role. A teleport process can have any combination of 3 roles: auth, node, proxy
// and they have their own identities
Expand Down Expand Up @@ -2527,23 +2547,25 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
return trace.Wrap(err)
}
}

webHandler, err = web.NewHandler(
web.Config{
Proxy: tsrv,
AuthServers: cfg.AuthServers[0],
DomainName: cfg.Hostname,
ProxyClient: conn.Client,
ProxySSHAddr: proxySSHAddr,
ProxyWebAddr: cfg.Proxy.WebAddr,
ProxySettings: proxySettings,
CipherSuites: cfg.CipherSuites,
FIPS: cfg.FIPS,
AccessPoint: accessPoint,
Emitter: streamEmitter,
PluginRegistry: process.PluginRegistry,
HostUUID: process.Config.HostUUID,
Context: process.ExitContext(),
StaticFS: fs,
Proxy: tsrv,
AuthServers: cfg.AuthServers[0],
DomainName: cfg.Hostname,
ProxyClient: conn.Client,
ProxySSHAddr: proxySSHAddr,
ProxyWebAddr: cfg.Proxy.WebAddr,
ProxySettings: proxySettings,
CipherSuites: cfg.CipherSuites,
FIPS: cfg.FIPS,
AccessPoint: accessPoint,
Emitter: streamEmitter,
PluginRegistry: process.PluginRegistry,
HostUUID: process.Config.HostUUID,
Context: process.ExitContext(),
StaticFS: fs,
ClusterFeatures: process.getClusterFeatures(),
})
if err != nil {
return trace.Wrap(err)
Expand Down
24 changes: 19 additions & 5 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"time"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
Expand Down Expand Up @@ -84,6 +85,9 @@ type Handler struct {
// sshPort specifies the SSH proxy port extracted
// from configuration
sshPort string

// clusterFeatures contain flags for supported and unsupported features.
clusterFeatures proto.Features
}

// HandlerOption is a functional argument - an option that can be passed
Expand Down Expand Up @@ -157,6 +161,9 @@ type Config struct {
// in the cache before getting purged after it has expired.
// Defaults to cachedSessionLingeringThreshold if unspecified.
cachedSessionLingeringThreshold *time.Duration

// ClusterFeatures contains flags for supported/unsupported features.
ClusterFeatures proto.Features
}

type RewritingHandler struct {
Expand Down Expand Up @@ -197,9 +204,10 @@ func (h *RewritingHandler) Close() error {
func NewHandler(cfg Config, opts ...HandlerOption) (*RewritingHandler, error) {
const apiPrefix = "/" + teleport.WebAPIVersion
h := &Handler{
cfg: cfg,
log: newPackageLogger(),
clock: clockwork.NewRealClock(),
cfg: cfg,
log: newPackageLogger(),
clock: clockwork.NewRealClock(),
clusterFeatures: cfg.ClusterFeatures,
}

for _, o := range opts {
Expand Down Expand Up @@ -284,7 +292,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*RewritingHandler, error) {
h.GET("/webapi/sites/:site/namespaces/:namespace/nodes", h.WithClusterAuth(h.siteNodesGet))

// Get applications.
h.GET("/webapi/sites/:site/apps", h.WithClusterAuth(h.siteAppsGet))
h.GET("/webapi/sites/:site/apps", h.WithClusterAuth(h.clusterAppsGet))

// active sessions handlers
h.GET("/webapi/sites/:site/namespaces/:namespace/connect", h.WithClusterAuth(h.siteNodeConnect)) // connect to an active session (via websocket)
Expand All @@ -305,6 +313,12 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*RewritingHandler, error) {
// web context
h.GET("/webapi/sites/:site/context", h.WithClusterAuth(h.getUserContext))

// Database access handlers.
h.GET("/webapi/sites/:site/databases", h.WithClusterAuth(h.clusterDatabasesGet))

// Kube access handlers.
h.GET("/webapi/sites/:site/kubernetes", h.WithClusterAuth(h.clusterKubesGet))

// OIDC related callback handlers
h.GET("/webapi/oidc/login/web", h.WithRedirect(h.oidcLoginWeb))
h.GET("/webapi/oidc/callback", h.WithRedirect(h.oidcCallback))
Expand Down Expand Up @@ -501,7 +515,7 @@ func (h *Handler) getUserContext(w http.ResponseWriter, r *http.Request, p httpr
return nil, trace.Wrap(err)
}

userContext, err := ui.NewUserContext(user, roleset)
userContext, err := ui.NewUserContext(user, roleset, h.clusterFeatures)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
83 changes: 83 additions & 0 deletions lib/web/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1806,6 +1806,89 @@ func (m *testModules) Features() modules.Features {
}
}

func TestClusterDatabasesGet(t *testing.T) {
env := newWebPack(t, 1)

proxy := env.proxies[0]
pack := proxy.authPack(t, "[email protected]")

endpoint := pack.clt.Endpoint("webapi", "sites", env.server.ClusterName(), "databases")
re, err := pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

// No db registered.
dbs := []ui.Database{}
require.NoError(t, json.Unmarshal(re.Bytes(), &dbs))
require.Len(t, dbs, 0)

// Register a database.
db := types.NewDatabaseServerV3("test-db-name", map[string]string{"test-field": "test-value"}, types.DatabaseServerSpecV3{
Description: "test-description",
Protocol: "test-protocol",
URI: "test-uri",
Hostname: "test-hostname",
HostID: "test-hostID",
})

_, err = env.server.Auth().UpsertDatabaseServer(context.Background(), db)
require.NoError(t, err)

re, err = pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

dbs = []ui.Database{}
require.NoError(t, json.Unmarshal(re.Bytes(), &dbs))
require.Len(t, dbs, 1)
require.EqualValues(t, ui.Database{
Name: "test-db-name",
Desc: "test-description",
Protocol: "test-protocol",
Type: types.DatabaseTypeSelfHosted,
Labels: []ui.Label{{Name: "test-field", Value: "test-value"}},
}, dbs[0])
}

func TestClusterKubesGet(t *testing.T) {
env := newWebPack(t, 1)

proxy := env.proxies[0]
pack := proxy.authPack(t, "[email protected]")

endpoint := pack.clt.Endpoint("webapi", "sites", env.server.ClusterName(), "kubernetes")
re, err := pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

// No kube registered.
kbs := []ui.Kube{}
require.NoError(t, json.Unmarshal(re.Bytes(), &kbs))
require.Len(t, kbs, 0)

// Register a kube service.
err = env.server.Auth().UpsertKubeService(context.Background(), &services.ServerV2{
Metadata: services.Metadata{Name: "test-kube"},
Kind: services.KindKubeService,
Version: services.V2,
Spec: services.ServerSpecV2{
KubernetesClusters: []*services.KubernetesCluster{{
Name: "test-kube-name",
StaticLabels: map[string]string{"test-field": "test-value"},
}},
},
})
require.NoError(t, err)

re, err = pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

kbs = []ui.Kube{}
require.NoError(t, json.Unmarshal(re.Bytes(), &kbs))
require.Len(t, kbs, 1)
require.EqualValues(t, ui.Kube{
Name: "test-kube-name",
Labels: []ui.Label{{Name: "test-field", Value: "test-value"}},
}, kbs[0])
}

// TestApplicationAccessDisabled makes sure application access can be disabled
// via modules.
func TestApplicationAccessDisabled(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions lib/web/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import (
"github.com/julienschmidt/httprouter"
)

// siteAppsGet returns a list of applications in a form the UI can present.
func (h *Handler) siteAppsGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
// clusterAppsGet returns a list of applications in a form the UI can present.
func (h *Handler) clusterAppsGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
appClusterName := p.ByName("site")

// Get a list of application servers.
Expand Down
59 changes: 59 additions & 0 deletions lib/web/servers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* 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/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/web/ui"
"github.com/gravitational/trace"
"github.com/julienschmidt/httprouter"
)

// 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, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
clt, err := ctx.GetUserClient(site)
if err != nil {
return nil, trace.Wrap(err)
}

// Get a list of kube servers.
kubeServers, err := clt.GetKubeServices(r.Context())
if err != nil {
return nil, trace.Wrap(err)
}

return ui.MakeKubes(h.auth.clusterName, kubeServers), 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, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
clt, err := ctx.GetUserClient(site)
if err != nil {
return nil, trace.Wrap(err)
}

// Get a list of database servers.
dbServers, err := clt.GetDatabaseServers(r.Context(), defaults.Namespace)
if err != nil {
return nil, trace.Wrap(err)
}

return ui.MakeDatabases(h.auth.clusterName, dbServers), nil
}
18 changes: 7 additions & 11 deletions lib/web/ui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,11 @@ package ui

import (
"fmt"
"sort"

"github.com/gravitational/teleport/lib/services"
)

// AppLabel describes an application label
type AppLabel struct {
// Name is this label name
Name string `json:"name"`
// Value is this label value
Value string `json:"value"`
}

// App describes an application
type App struct {
// Name is the name of the application.
Expand All @@ -43,7 +36,7 @@ type App struct {
// ClusterID is this app cluster ID
ClusterID string `json:"clusterId"`
// Labels is a map of static labels associated with an application.
Labels []AppLabel `json:"labels"`
Labels []Label `json:"labels"`
}

// MakeApps creates server application objects
Expand All @@ -53,13 +46,16 @@ func MakeApps(localClusterName string, localProxyDNSName string, appClusterName
teleApps := server.GetApps()
for _, teleApp := range teleApps {
fqdn := AssembleAppFQDN(localClusterName, localProxyDNSName, appClusterName, teleApp)
labels := []AppLabel{}
labels := []Label{}
for name, value := range teleApp.StaticLabels {
labels = append(labels, AppLabel{
labels = append(labels, Label{
Name: name,
Value: value,
})
}

sort.Sort(sortedLabels(labels))

result = append(result, App{
Name: teleApp.Name,
URI: teleApp.URI,
Expand Down
Loading

0 comments on commit 8e677f2

Please sign in to comment.