Migrate kube_service CRUD endpoints to gRPC (#4792)

The REST endpoints weren't used in any release yet, so we don't need to
worry about backwards-compatibility.
This commit is contained in:
Andrew Lytvynov
2020-11-12 18:35:34 +00:00
committed by GitHub
parent 450f3e7b81
commit 271d7ea4e7
10 changed files with 1342 additions and 299 deletions
-41
View File
@@ -140,11 +140,6 @@ func NewAPIServer(config *APIConfig) http.Handler {
srv.DELETE("/:version/tunnelconnections/:cluster/:conn", srv.withAuth(srv.deleteTunnelConnection))
srv.DELETE("/:version/tunnelconnections/:cluster", srv.withAuth(srv.deleteTunnelConnections))
srv.DELETE("/:version/tunnelconnections", srv.withAuth(srv.deleteAllTunnelConnections))
// TODO(awly): migrate these to the gRPC service.
srv.POST("/:version/kube_services", srv.withAuth(srv.upsertKubeService))
srv.GET("/:version/kube_services", srv.withAuth(srv.getKubeServices))
srv.DELETE("/:version/kube_services/:name", srv.withAuth(srv.deleteKubeService))
srv.DELETE("/:version/kube_services", srv.withAuth(srv.deleteAllKubeServices))
// Server Credentials
srv.POST("/:version/server/credentials", srv.withAuth(srv.generateServerKeys))
@@ -340,8 +335,6 @@ func (s *APIServer) upsertServer(auth services.Presence, role teleport.Role, r *
kind = services.KindAuthServer
case teleport.RoleProxy:
kind = services.KindProxy
case teleport.RoleKube:
kind = services.KindKubeService
default:
return nil, trace.BadParameter("upsertServer with unknown role: %q", role)
}
@@ -375,10 +368,6 @@ func (s *APIServer) upsertServer(auth services.Presence, role teleport.Role, r *
if err := auth.UpsertProxy(server); err != nil {
return nil, trace.Wrap(err)
}
case teleport.RoleKube:
if err := auth.UpsertKubeService(r.Context(), server); err != nil {
return nil, trace.Wrap(err)
}
default:
return nil, trace.BadParameter("unknown server role %q", role)
}
@@ -2497,36 +2486,6 @@ func (s *APIServer) getServerID(r *http.Request) (string, error) {
return strings.TrimSuffix(role.Username, "."+clusterName), nil
}
func (s *APIServer) upsertKubeService(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
return s.upsertServer(auth, teleport.RoleKube, r, p)
}
func (s *APIServer) getKubeServices(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
servers, err := auth.GetKubeServices(r.Context())
if err != nil {
return nil, trace.Wrap(err)
}
return marshalServers(servers, version)
}
func (s *APIServer) deleteKubeService(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
name := p.ByName("name")
if name == "" {
return nil, trace.BadParameter("missing kubernetes service name")
}
if err := auth.DeleteKubeService(r.Context(), name); err != nil {
return nil, trace.Wrap(err)
}
return message("ok"), nil
}
func (s *APIServer) deleteAllKubeServices(auth ClientI, w http.ResponseWriter, r *http.Request, p httprouter.Params, version string) (interface{}, error) {
if err := auth.DeleteAllKubeServices(r.Context()); err != nil {
return nil, trace.Wrap(err)
}
return message("ok"), nil
}
func message(msg string) map[string]interface{} {
return map[string]interface{}{"message": msg}
}
-18
View File
@@ -18,7 +18,6 @@ package auth
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
@@ -35,7 +34,6 @@ import (
func TestUpsertServer(t *testing.T) {
t.Parallel()
ctx := context.TODO()
const remoteAddr = "request-remote-addr"
tests := []struct {
@@ -90,21 +88,6 @@ func TestUpsertServer(t *testing.T) {
},
assertErr: require.NoError,
},
{
desc: "kubernetes",
reqServer: &services.ServerV2{
Metadata: services.Metadata{Name: "test-server", Namespace: defaults.Namespace},
Version: services.V2,
Kind: services.KindKubeService,
},
role: teleport.RoleKube,
wantServer: &services.ServerV2{
Metadata: services.Metadata{Name: "test-server", Namespace: defaults.Namespace},
Version: services.V2,
Kind: services.KindKubeService,
},
assertErr: require.NoError,
},
{
desc: "unknown",
reqServer: &services.ServerV2{
@@ -146,7 +129,6 @@ func TestUpsertServer(t *testing.T) {
addServers(s.GetAuthServers())
addServers(s.GetNodes(defaults.Namespace))
addServers(s.GetProxies())
addServers(s.GetKubeServices(ctx))
require.Empty(t, cmp.Diff(allServers, []services.Server{tt.wantServer}))
})
}
+2 -4
View File
@@ -870,19 +870,17 @@ func (a *ServerWithRoles) Ping(ctx context.Context) (proto.PingResponse, error)
}, nil
}
type contextKey string
// WithDelegator creates a child context with the AccessRequestDelegator
// value set. Optionally used by AuthServer.SetAccessRequestState to log
// a delegating identity.
func WithDelegator(ctx context.Context, delegator string) context.Context {
return context.WithValue(ctx, contextKey(events.AccessRequestDelegator), delegator)
return context.WithValue(ctx, ContextDelegator, delegator)
}
// getDelegator attempts to load the context value AccessRequestDelegator,
// returning the empty string if no value was found.
func getDelegator(ctx context.Context) string {
delegator, ok := ctx.Value(contextKey(events.AccessRequestDelegator)).(string)
delegator, ok := ctx.Value(ContextDelegator).(string)
if !ok {
return ""
}
+27 -17
View File
@@ -2952,37 +2952,37 @@ func (c *Client) DeleteSemaphore(ctx context.Context, filter services.SemaphoreF
// UpsertKubeService is used by kubernetes services to report their presence
// to other auth servers in form of hearbeat expiring after ttl period.
func (c *Client) UpsertKubeService(ctx context.Context, s services.Server) error {
data, err := services.GetServerMarshaler().MarshalServer(s)
clt, err := c.grpc()
if err != nil {
return trace.Wrap(err)
}
args := &upsertServerRawReq{
Server: data,
server, ok := s.(*services.ServerV2)
if !ok {
return trace.BadParameter("invalid type %T, expected *services.ServerV2", server)
}
_, err = c.PostJSON(c.Endpoint("kube_services"), args)
_, err = clt.UpsertKubeService(ctx, &proto.UpsertKubeServiceRequest{
Server: server,
})
return trace.Wrap(err)
}
// GetKubeServices returns the list of kubernetes services registered in the
// cluster.
func (c *Client) GetKubeServices(ctx context.Context) ([]services.Server, error) {
out, err := c.Get(c.Endpoint("kube_services"), url.Values{})
clt, err := c.grpc()
if err != nil {
return nil, trace.Wrap(err)
}
var items []json.RawMessage
if err := json.Unmarshal(out.Bytes(), &items); err != nil {
resp, err := clt.GetKubeServices(ctx, &proto.GetKubeServicesRequest{})
if err != nil {
return nil, trace.Wrap(err)
}
re := make([]services.Server, len(items))
for i, raw := range items {
server, err := services.GetServerMarshaler().UnmarshalServer(raw, services.KindKubeService, services.SkipValidation())
if err != nil {
return nil, trace.Wrap(err)
}
re[i] = server
var servers []services.Server
for _, server := range resp.GetServers() {
servers = append(servers, server)
}
return re, nil
return servers, nil
}
// GetAppServers gets all application servers.
@@ -3184,13 +3184,23 @@ func (c *Client) GenerateAppToken(ctx context.Context, req jwt.GenerateAppTokenR
// DeleteKubeService deletes a named kubernetes service.
func (c *Client) DeleteKubeService(ctx context.Context, name string) error {
_, err := c.Delete(c.Endpoint("kube_services", name))
clt, err := c.grpc()
if err != nil {
return trace.Wrap(err)
}
_, err = clt.DeleteKubeService(ctx, &proto.DeleteKubeServiceRequest{
Name: name,
})
return trace.Wrap(err)
}
// DeleteAllKubeServices deletes all registered kubernetes services.
func (c *Client) DeleteAllKubeServices(ctx context.Context) error {
_, err := c.Delete(c.Endpoint("kube_services"))
clt, err := c.grpc()
if err != nil {
return trace.Wrap(err)
}
_, err = clt.DeleteAllKubeServices(ctx, &proto.DeleteAllKubeServicesRequest{})
return trace.Wrap(err)
}
+86
View File
@@ -20,6 +20,7 @@ import (
"context"
"crypto/tls"
"io"
"net"
"time"
"github.com/gravitational/teleport"
@@ -37,8 +38,10 @@ import (
"github.com/gravitational/trace/trail"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
// Register gzip compressor for gRPC.
_ "google.golang.org/grpc/encoding/gzip"
@@ -861,6 +864,89 @@ func (g *GRPCServer) UpdateRemoteCluster(ctx context.Context, req *services.Remo
return &empty.Empty{}, nil
}
// GetKubeServices gets all kubernetes services.
func (g *GRPCServer) GetKubeServices(ctx context.Context, req *proto.GetKubeServicesRequest) (*proto.GetKubeServicesResponse, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trail.ToGRPC(err)
}
kubeServices, err := auth.GetKubeServices(ctx)
if err != nil {
return nil, trail.ToGRPC(err)
}
var servers []*services.ServerV2
for _, s := range kubeServices {
server, ok := s.(*services.ServerV2)
if !ok {
return nil, trail.ToGRPC(trace.BadParameter("unexpected type %T", s))
}
servers = append(servers, server)
}
return &proto.GetKubeServicesResponse{
Servers: servers,
}, nil
}
// UpsertKubeService adds a kubernetes service.
func (g *GRPCServer) UpsertKubeService(ctx context.Context, req *proto.UpsertKubeServiceRequest) (*empty.Empty, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trail.ToGRPC(err)
}
server := req.GetServer()
// If Addr in the server is localhost, replace it with the address we see
// from our end.
//
// Services that listen on "0.0.0.0:12345" will put that exact address in
// the server.Addr field. It's not useful for other services that want to
// connect to it (like a proxy). Remote address of the gRPC connection is
// the closest thing we have to a public IP for the service.
clientAddr, ok := ctx.Value(ContextClientAddr).(net.Addr)
if !ok {
return nil, status.Errorf(codes.FailedPrecondition, "bug: client address not found in request context")
}
server.SetAddr(utils.ReplaceLocalhost(server.GetAddr(), clientAddr.String()))
if err := auth.UpsertKubeService(ctx, server); err != nil {
return nil, trail.ToGRPC(err)
}
return new(empty.Empty), nil
}
// DeleteKubeService removes a kubernetes service.
func (g *GRPCServer) DeleteKubeService(ctx context.Context, req *proto.DeleteKubeServiceRequest) (*empty.Empty, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trail.ToGRPC(err)
}
err = auth.DeleteKubeService(ctx, req.GetName())
if err != nil {
return nil, trail.ToGRPC(err)
}
return &empty.Empty{}, nil
}
// DeleteAllKubeServices removes all kubernetes services.
func (g *GRPCServer) DeleteAllKubeServices(ctx context.Context, req *proto.DeleteAllKubeServicesRequest) (*empty.Empty, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trail.ToGRPC(err)
}
err = auth.DeleteAllKubeServices(ctx)
if err != nil {
return nil, trail.ToGRPC(err)
}
return &empty.Empty{}, nil
}
type grpcContext struct {
*Context
*ServerWithRoles
+1
View File
@@ -330,6 +330,7 @@ func (a *Middleware) UnaryInterceptor(ctx context.Context, req interface{}, info
return nil, trail.ToGRPC(trace.LimitExceeded("connection limit exceeded"))
}
defer a.Limiter.ConnLimiter.Release(clientIP, 1)
ctx = context.WithValue(ctx, ContextClientAddr, peerInfo.Addr)
tlsInfo, ok := peerInfo.AuthInfo.(credentials.TLSInfo)
if !ok {
+11 -3
View File
@@ -22,6 +22,7 @@ import (
"strings"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
@@ -517,10 +518,17 @@ func contextForLocalUser(u LocalUser, identity services.UserGetter, access servi
}, nil
}
type contextUserKey string
type contextKey string
// ContextUser is a user set in the context of the request
const ContextUser contextUserKey = "teleport-user"
const (
// ContextUser is a user set in the context of the request
ContextUser contextKey = "teleport-user"
// ContextClientAddr is a client address set in the context of the request
ContextClientAddr contextKey = "client-addr"
// ContextDelegator is a delegator for access requests set in the context
// of the request
ContextDelegator contextKey = events.AccessRequestDelegator
)
// clientUsername returns the username of a remote HTTP client making the call.
// If ctx didn't pass through auth middleware or did not come from an HTTP
File diff suppressed because it is too large Load Diff
+35
View File
@@ -367,6 +367,32 @@ message CreateAppSessionResponse {
// DeleteAppSessionRequest contains the parameters used to remove an application web session.
message DeleteAppSessionRequest { string SessionID = 1 [ (gogoproto.jsontag) = "session_id" ]; }
// GetKubeServicesRequest are the parameters used to request kubernetes services.
message GetKubeServicesRequest {
}
// GetKubeServicesResponse contains all requested kubernetes services.
message GetKubeServicesResponse {
// Servers is a slice of services.Server that represent kubernetes
// services.
repeated services.ServerV2 Servers = 1 [ (gogoproto.jsontag) = "servers" ];
}
// UpsertKubeServiceRequest are the parameters used to add or update a
// kubernetes service.
message UpsertKubeServiceRequest {
services.ServerV2 Server = 1 [ (gogoproto.jsontag) = "server" ];
}
// DeleteKubeServiceRequest are the parameters used to remove a kubernetes service.
message DeleteKubeServiceRequest {
// Name is the name of the kubernetes service to delete.
string Name = 2 [ (gogoproto.jsontag) = "name" ];
}
// DeleteAllKubeServicesRequest are the parameters used to remove all kubernetes services.
message DeleteAllKubeServicesRequest {}
// AuthService is authentication/authorization service implementation
service AuthService {
// SendKeepAlives allows node to send a stream of keep alive requests
@@ -455,4 +481,13 @@ service AuthService {
// UpdateRemoteCluster updates remote cluster
rpc UpdateRemoteCluster(services.RemoteClusterV3) returns (google.protobuf.Empty);
// GetKubeServices gets all kubernetes services.
rpc GetKubeServices(GetKubeServicesRequest) returns (GetKubeServicesResponse);
// UpsertKubeService adds or updates a kubernetes service.
rpc UpsertKubeService(UpsertKubeServiceRequest) returns (google.protobuf.Empty);
// DeleteKubeService removes a kubernetes service.
rpc DeleteKubeService(DeleteKubeServiceRequest) returns (google.protobuf.Empty);
// DeleteAllKubeServices removes all kubernetes services.
rpc DeleteAllKubeServices(DeleteAllKubeServicesRequest) returns (google.protobuf.Empty);
}
+3
View File
@@ -1018,6 +1018,9 @@ func (s *PresenceService) GetKubeServices(ctx context.Context) ([]services.Serve
// DeleteKubeService deletes a named kubernetes service.
func (s *PresenceService) DeleteKubeService(ctx context.Context, name string) error {
if name == "" {
return trace.BadParameter("no name specified for kubernetes service deletion")
}
return trace.Wrap(s.Delete(ctx, backend.Key(kubeServicesPrefix, name)))
}