mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-12 02:15:01 +00:00
* hack on migrating list RPC * Fix tests * Tidy up/fix tests * Use `clientutils.CollectWithFallback` * Use clearer c.APIClient rather than rely on embeding * Cover ListTunnelConnections in cache test * tweak logging * update deprecation version * Move fallback impls * fix span ctx prop * fix pagination bug when unmarshal bad * USe t.Context() * fix ctx propagation * Update tunn conns to use range pattern - thanks luke * fix unneeded dependency * fix deprecation * env: dev use opaque API
361 lines
12 KiB
Go
361 lines
12 KiB
Go
/*
|
|
* Teleport
|
|
* Copyright (C) 2023 Gravitational, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package authclient
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/url"
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
presencev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1"
|
|
trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/api/utils/clientutils"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
)
|
|
|
|
// httpfallback.go holds endpoints that have been converted to gRPC
|
|
// but still need http fallback logic in the old client.
|
|
|
|
// ValidateTrustedCluster is called by the proxy on behalf of a cluster that
|
|
// wishes to join another as a leaf cluster.
|
|
func (c *Client) ValidateTrustedCluster(ctx context.Context, validateRequest *ValidateTrustedClusterRequest) (*ValidateTrustedClusterResponse, error) {
|
|
protoReq, err := validateRequest.ToProto()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "converting native ValidateTrustedClusterRequest to proto")
|
|
}
|
|
protoResp, err := c.APIClient.ValidateTrustedCluster(ctx, protoReq)
|
|
if err != nil {
|
|
if trace.IsNotImplemented(err) {
|
|
return c.HTTPClient.validateTrustedCluster(ctx, validateRequest)
|
|
}
|
|
return nil, trace.Wrap(err, "calling ValidateTrustedCluster on gRPC client")
|
|
}
|
|
return ValidateTrustedClusterResponseFromProto(protoResp), nil
|
|
}
|
|
|
|
// TODO(noah): DELETE IN 21.0.0
|
|
func (c *HTTPClient) validateTrustedCluster(ctx context.Context, validateRequest *ValidateTrustedClusterRequest) (*ValidateTrustedClusterResponse, error) {
|
|
validateRequestRaw, err := validateRequest.ToRaw()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
out, err := c.PostJSON(ctx, c.Endpoint("trustedclusters", "validate"), validateRequestRaw)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
var validateResponseRaw ValidateTrustedClusterResponseRaw
|
|
err = json.Unmarshal(out.Bytes(), &validateResponseRaw)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
validateResponse, err := validateResponseRaw.ToNative()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return validateResponse, nil
|
|
}
|
|
|
|
// UpsertTunnelConnection creates or updates a tunnel connection record.
|
|
//
|
|
// TODO(strideynet): DELETE IN v20.0.0
|
|
func (c *Client) UpsertTunnelConnection(ctx context.Context, conn types.TunnelConnection) error {
|
|
connV2, ok := conn.(*types.TunnelConnectionV2)
|
|
if !ok {
|
|
return trace.BadParameter("unsupported tunnel connection type %T", conn)
|
|
}
|
|
_, err := c.TrustClient().UpsertTunnelConnection(ctx, trustpb.UpsertTunnelConnectionRequest_builder{
|
|
TunnelConnection: connV2,
|
|
}.Build())
|
|
if err != nil {
|
|
if trace.IsNotImplemented(err) {
|
|
return trace.Wrap(c.HTTPClient.upsertTunnelConnection(ctx, conn))
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TODO(strideynet): DELETE IN v20.0.0
|
|
func (c *HTTPClient) upsertTunnelConnection(ctx context.Context, conn types.TunnelConnection) error {
|
|
data, err := services.MarshalTunnelConnection(conn)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
args := &struct {
|
|
TunnelConnection json.RawMessage `json:"tunnel_connection"`
|
|
}{
|
|
TunnelConnection: data,
|
|
}
|
|
_, err = c.PostJSON(ctx, c.Endpoint("tunnelconnections"), args)
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// DeleteTunnelConnection removes a tunnel connection by cluster and connection name.
|
|
//
|
|
// TODO(strideynet): DELETE IN v20.0.0
|
|
func (c *Client) DeleteTunnelConnection(ctx context.Context, clusterName, connName string) error {
|
|
_, err := c.TrustClient().DeleteTunnelConnection(ctx, trustpb.DeleteTunnelConnectionRequest_builder{
|
|
ClusterName: clusterName,
|
|
ConnectionName: connName,
|
|
}.Build())
|
|
if err != nil {
|
|
if trace.IsNotImplemented(err) {
|
|
return trace.Wrap(c.HTTPClient.deleteTunnelConnection(ctx, clusterName, connName))
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// TODO(strideynet): DELETE IN v20.0.0
|
|
func (c *HTTPClient) deleteTunnelConnection(ctx context.Context, clusterName, connName string) error {
|
|
if clusterName == "" {
|
|
return trace.BadParameter("missing parameter cluster name")
|
|
}
|
|
if connName == "" {
|
|
return trace.BadParameter("missing parameter connection name")
|
|
}
|
|
_, err := c.Delete(ctx, c.Endpoint("tunnelconnections", clusterName, connName))
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// GetTunnelConnections returns all tunnel connections for a given cluster.
|
|
//
|
|
// TODO(noah): DELETE IN 20.0.0 — inline the gRPC page-loop directly once the
|
|
// HTTP fallback is removed; keep the method signature.
|
|
func (c *Client) GetTunnelConnections(ctx context.Context, clusterName string) ([]types.TunnelConnection, error) {
|
|
if clusterName == "" {
|
|
return nil, trace.BadParameter("missing cluster name parameter")
|
|
}
|
|
return c.listAllTunnelConnections(ctx, clusterName)
|
|
}
|
|
|
|
// GetAllTunnelConnections returns all tunnel connections across all clusters.
|
|
//
|
|
// TODO(noah): DELETE IN 20.0.0
|
|
func (c *Client) GetAllTunnelConnections(ctx context.Context) ([]types.TunnelConnection, error) {
|
|
return c.listAllTunnelConnections(ctx, "")
|
|
}
|
|
|
|
// TODO(noah): DELETE IN 20.0.0
|
|
func (c *Client) listAllTunnelConnections(ctx context.Context, clusterName string) ([]types.TunnelConnection, error) {
|
|
var filter *trustpb.ListTunnelConnectionsFilter
|
|
if clusterName != "" {
|
|
filter = &trustpb.ListTunnelConnectionsFilter{ClusterName: clusterName}
|
|
}
|
|
|
|
items, err := clientutils.CollectWithFallback(
|
|
ctx,
|
|
func(
|
|
ctx context.Context, pageSize int, pageToken string,
|
|
) ([]types.TunnelConnection, string, error) {
|
|
return c.APIClient.ListTunnelConnections(ctx, pageSize, pageToken, filter)
|
|
},
|
|
func(ctx context.Context) ([]types.TunnelConnection, error) {
|
|
if clusterName != "" {
|
|
return c.HTTPClient.getTunnelConnectionsLegacy(ctx, clusterName)
|
|
}
|
|
return c.HTTPClient.getAllTunnelConnectionsLegacy(ctx)
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
// getTunnelConnectionsLegacy returns tunnel connections for a given cluster.
|
|
//
|
|
// TODO(noah): DELETE IN 20.0.0
|
|
func (c *HTTPClient) getTunnelConnectionsLegacy(ctx context.Context, clusterName string) ([]types.TunnelConnection, error) {
|
|
if clusterName == "" {
|
|
return nil, trace.BadParameter("missing cluster name parameter")
|
|
}
|
|
out, err := c.Get(ctx, c.Endpoint("tunnelconnections", clusterName), url.Values{})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var items []json.RawMessage
|
|
if err := json.Unmarshal(out.Bytes(), &items); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
conns := make([]types.TunnelConnection, len(items))
|
|
for i, raw := range items {
|
|
conn, err := services.UnmarshalTunnelConnection(raw)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
conns[i] = conn
|
|
}
|
|
return conns, nil
|
|
}
|
|
|
|
// getAllTunnelConnectionsLegacy returns all tunnel connections.
|
|
//
|
|
// TODO(noah): DELETE IN 20.0.0
|
|
func (c *HTTPClient) getAllTunnelConnectionsLegacy(ctx context.Context) ([]types.TunnelConnection, error) {
|
|
out, err := c.Get(ctx, c.Endpoint("tunnelconnections"), url.Values{})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var items []json.RawMessage
|
|
if err := json.Unmarshal(out.Bytes(), &items); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
conns := make([]types.TunnelConnection, len(items))
|
|
for i, raw := range items {
|
|
conn, err := services.UnmarshalTunnelConnection(raw)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
conns[i] = conn
|
|
}
|
|
return conns, nil
|
|
}
|
|
|
|
// GetAuthServers returns the list of auth servers registered in the cluster.
|
|
//
|
|
// Deprecated: Prefer paginated variant [APIClient.ListAuthServers].
|
|
//
|
|
// TODO(kiosion): DELETE IN 21.0.0
|
|
func (c *HTTPClient) GetAuthServers() ([]types.Server, error) {
|
|
out, err := c.Get(context.TODO(), c.Endpoint("authservers"), url.Values{})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var items []json.RawMessage
|
|
if err := json.Unmarshal(out.Bytes(), &items); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
re := make([]types.Server, len(items))
|
|
for i, raw := range items {
|
|
server, err := services.UnmarshalServer(raw, types.KindAuthServer)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
re[i] = server
|
|
}
|
|
return re, nil
|
|
}
|
|
|
|
// UpsertProxyServerWithoutReturn registers a proxy server heartbeat. It calls
|
|
// the gRPC PresenceService and falls back to the legacy HTTP endpoint if the
|
|
// server does not yet implement the gRPC RPC. The upserted proxy server is not
|
|
// returned because the HTTP fallback path cannot provide it; once the fallback
|
|
// is removed in v20 this can be replaced with a method that returns the
|
|
// upserted server.
|
|
//
|
|
// TODO(noah): DELETE IN v20.0.0
|
|
func (c *Client) UpsertProxyServerWithoutReturn(ctx context.Context, s types.Server) error {
|
|
serverV2, ok := s.(*types.ServerV2)
|
|
if !ok {
|
|
return trace.BadParameter("unsupported proxy server type %T", s)
|
|
}
|
|
_, err := c.APIClient.PresenceServiceClient().UpsertProxyServer(ctx, presencev1.UpsertProxyServerRequest_builder{
|
|
Server: serverV2,
|
|
}.Build())
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if !trace.IsNotImplemented(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
return c.HTTPClient.upsertProxyServerLegacy(ctx, s)
|
|
}
|
|
|
|
// upsertProxyServerLegacy registers a proxy server heartbeat via the legacy
|
|
// HTTP endpoint.
|
|
//
|
|
// TODO(noah): DELETE IN v20.0.0
|
|
func (c *HTTPClient) upsertProxyServerLegacy(ctx context.Context, s types.Server) error {
|
|
data, err := services.MarshalServer(s)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
args := &upsertServerRawReq{
|
|
Server: data,
|
|
}
|
|
_, err = c.PostJSON(ctx, c.Endpoint("proxies"), args)
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// DeleteProxyServer deletes a proxy server heartbeat by name. It calls the
|
|
// gRPC PresenceService and falls back to the legacy HTTP endpoint if the
|
|
// server does not yet implement the gRPC RPC.
|
|
//
|
|
// TODO(noah): DELETE IN v20.0.0
|
|
func (c *Client) DeleteProxyServer(ctx context.Context, name string) error {
|
|
_, err := c.APIClient.PresenceServiceClient().DeleteProxyServer(ctx, presencev1.DeleteProxyServerRequest_builder{
|
|
Name: name,
|
|
}.Build())
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if !trace.IsNotImplemented(err) {
|
|
return trace.Wrap(err)
|
|
}
|
|
return c.HTTPClient.deleteProxyServerLegacy(ctx, name)
|
|
}
|
|
|
|
// deleteProxyServerLegacy deletes proxy by name via the legacy HTTP endpoint.
|
|
//
|
|
// TODO(noah): DELETE IN v20.0.0
|
|
func (c *HTTPClient) deleteProxyServerLegacy(ctx context.Context, name string) error {
|
|
if name == "" {
|
|
return trace.BadParameter("missing parameter name")
|
|
}
|
|
_, err := c.Delete(ctx, c.Endpoint("proxies", name))
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetProxies returns the list of auth servers registered in the cluster.
|
|
//
|
|
// Deprecated: Prefer paginated variant [APIClient.ListProxyServers].
|
|
//
|
|
// TODO(kiosion): DELETE IN 21.0.0
|
|
func (c *HTTPClient) GetProxies() ([]types.Server, error) {
|
|
out, err := c.Get(context.TODO(), c.Endpoint("proxies"), url.Values{})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var items []json.RawMessage
|
|
if err := json.Unmarshal(out.Bytes(), &items); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
re := make([]types.Server, len(items))
|
|
for i, raw := range items {
|
|
server, err := services.UnmarshalServer(raw, types.KindProxy)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
re[i] = server
|
|
}
|
|
return re, nil
|
|
}
|