mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
* Delete audit stream version check for v13 * Remove string parsing for bpf min kernel version * Remove string parsing for min etcd version * Remove version string parsing for route not found * Remove string parsing for min mariadb version * Remove string parsing for min mongodb custom data version * Remove string parsing for min openat2 kernel version * Use consts in api/version.go * Generate api/version.go with a go command * Avoid using a global variable SemVersion * Keep but deprecate SemVersion in api * fix-license
5792 lines
187 KiB
Go
5792 lines
187 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 auth
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/coreos/go-semver/semver"
|
|
"github.com/google/uuid"
|
|
"github.com/gravitational/trace"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
collectortracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
_ "google.golang.org/grpc/encoding/gzip" // gzip compressor for gRPC.
|
|
"google.golang.org/grpc/keepalive"
|
|
"google.golang.org/grpc/peer"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/emptypb"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/api/client"
|
|
authpb "github.com/gravitational/teleport/api/client/proto"
|
|
"github.com/gravitational/teleport/api/constants"
|
|
accessmonitoringrules "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1"
|
|
auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1"
|
|
autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
|
|
clusterconfigv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1"
|
|
crownjewelv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1"
|
|
dbobjectv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1"
|
|
dbobjectimportrulev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobjectimportrule/v1"
|
|
decisionpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/decision/v1alpha1"
|
|
discoveryconfigv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/discoveryconfig/v1"
|
|
dynamicwindowsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/dynamicwindows/v1"
|
|
gitserverv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/gitserver/v1"
|
|
healthcheckconfigv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/healthcheckconfig/v1"
|
|
identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1"
|
|
integrationv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1"
|
|
kubewaitingcontainerv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1"
|
|
loginrulev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1"
|
|
machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1"
|
|
mfav1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/mfa/v1"
|
|
notificationsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
|
|
presencev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1"
|
|
provisioningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/provisioning/v1"
|
|
stableunixusersv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/stableunixusers/v1"
|
|
trustv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1"
|
|
userloginstatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userloginstate/v1"
|
|
userprovisioningv2pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2"
|
|
usersv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1"
|
|
usertaskv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1"
|
|
vnetv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/vnet/v1"
|
|
workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1"
|
|
userpreferencesv1pb "github.com/gravitational/teleport/api/gen/proto/go/userpreferences/v1"
|
|
"github.com/gravitational/teleport/api/internalutils/stream"
|
|
"github.com/gravitational/teleport/api/metadata"
|
|
"github.com/gravitational/teleport/api/trail"
|
|
"github.com/gravitational/teleport/api/types"
|
|
apievents "github.com/gravitational/teleport/api/types/events"
|
|
"github.com/gravitational/teleport/api/types/installers"
|
|
"github.com/gravitational/teleport/api/types/wrappers"
|
|
apiutils "github.com/gravitational/teleport/api/utils"
|
|
"github.com/gravitational/teleport/lib/auth/accessmonitoringrules/accessmonitoringrulesv1"
|
|
"github.com/gravitational/teleport/lib/auth/authclient"
|
|
"github.com/gravitational/teleport/lib/auth/autoupdate/autoupdatev1"
|
|
"github.com/gravitational/teleport/lib/auth/clusterconfig/clusterconfigv1"
|
|
"github.com/gravitational/teleport/lib/auth/crownjewel/crownjewelv1"
|
|
"github.com/gravitational/teleport/lib/auth/dbobject/dbobjectv1"
|
|
"github.com/gravitational/teleport/lib/auth/dbobjectimportrule/dbobjectimportrulev1"
|
|
"github.com/gravitational/teleport/lib/auth/discoveryconfig/discoveryconfigv1"
|
|
"github.com/gravitational/teleport/lib/auth/dynamicwindows/dynamicwindowsv1"
|
|
"github.com/gravitational/teleport/lib/auth/gitserver/gitserverv1"
|
|
"github.com/gravitational/teleport/lib/auth/healthcheckconfig/healthcheckconfigv1"
|
|
"github.com/gravitational/teleport/lib/auth/integration/integrationv1"
|
|
"github.com/gravitational/teleport/lib/auth/kubewaitingcontainer/kubewaitingcontainerv1"
|
|
"github.com/gravitational/teleport/lib/auth/loginrule/loginrulev1"
|
|
"github.com/gravitational/teleport/lib/auth/machineid/machineidv1"
|
|
"github.com/gravitational/teleport/lib/auth/machineid/workloadidentityv1"
|
|
"github.com/gravitational/teleport/lib/auth/notifications/notificationsv1"
|
|
"github.com/gravitational/teleport/lib/auth/presence/presencev1"
|
|
"github.com/gravitational/teleport/lib/auth/stableunixusers"
|
|
"github.com/gravitational/teleport/lib/auth/trust/trustv1"
|
|
"github.com/gravitational/teleport/lib/auth/userloginstate/userloginstatev1"
|
|
"github.com/gravitational/teleport/lib/auth/userpreferences/userpreferencesv1"
|
|
"github.com/gravitational/teleport/lib/auth/userprovisioning/userprovisioningv2"
|
|
"github.com/gravitational/teleport/lib/auth/users/usersv1"
|
|
"github.com/gravitational/teleport/lib/auth/usertasks/usertasksv1"
|
|
"github.com/gravitational/teleport/lib/auth/vnetconfig/vnetconfigv1"
|
|
"github.com/gravitational/teleport/lib/authz"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/decision/decisionv1"
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/events"
|
|
"github.com/gravitational/teleport/lib/httplib"
|
|
"github.com/gravitational/teleport/lib/joinserver"
|
|
"github.com/gravitational/teleport/lib/modules"
|
|
"github.com/gravitational/teleport/lib/observability/metrics"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/services/local"
|
|
"github.com/gravitational/teleport/lib/session"
|
|
"github.com/gravitational/teleport/lib/srv/server/installer"
|
|
usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
logutils "github.com/gravitational/teleport/lib/utils/log"
|
|
)
|
|
|
|
var (
|
|
heartbeatConnectionsReceived = prometheus.NewCounter(
|
|
prometheus.CounterOpts{
|
|
Name: teleport.MetricHeartbeatConnectionsReceived,
|
|
Help: "Number of times auth received a heartbeat connection",
|
|
},
|
|
)
|
|
watcherEventsEmitted = prometheus.NewHistogramVec(
|
|
prometheus.HistogramOpts{
|
|
Name: teleport.MetricWatcherEventsEmitted,
|
|
Help: "Per resources size of events emitted",
|
|
Buckets: prometheus.LinearBuckets(0, 200, 5),
|
|
},
|
|
[]string{teleport.TagResource},
|
|
)
|
|
watcherEventSizes = prometheus.NewHistogram(
|
|
prometheus.HistogramOpts{
|
|
Name: teleport.MetricWatcherEventSizes,
|
|
Help: "Overall size of events emitted",
|
|
Buckets: prometheus.LinearBuckets(0, 100, 20),
|
|
},
|
|
)
|
|
connectedResources = prometheus.NewGaugeVec(
|
|
prometheus.GaugeOpts{
|
|
Namespace: teleport.MetricNamespace,
|
|
Name: teleport.MetricConnectedResources,
|
|
Help: "Tracks the number and type of resources connected via keepalives",
|
|
},
|
|
[]string{teleport.TagType},
|
|
)
|
|
|
|
createAuditStreamAcceptedTotalMetric = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Namespace: teleport.MetricNamespace,
|
|
Name: "unstable_createauditstream_accepted_total",
|
|
Help: "CreateAuditStream RPCs accepted by the concurrency limiter",
|
|
})
|
|
|
|
createAuditStreamRejectedTotalMetric = prometheus.NewCounter(prometheus.CounterOpts{
|
|
Namespace: teleport.MetricNamespace,
|
|
Name: "unstable_createauditstream_rejected_total",
|
|
Help: "CreateAuditStream RPCs rejected by the concurrency limiter",
|
|
})
|
|
|
|
createAuditStreamLimitMetric = prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Namespace: teleport.MetricNamespace,
|
|
Name: "unstable_createauditstream_limit",
|
|
Help: "Configured limit of in-flight CreateAuditStream RPCs",
|
|
})
|
|
)
|
|
|
|
// GRPCServer is gRPC Auth Server API
|
|
type GRPCServer struct {
|
|
authpb.UnimplementedAuthServiceServer
|
|
auditlogpb.UnimplementedAuditLogServiceServer
|
|
logger *slog.Logger
|
|
APIConfig
|
|
server *grpc.Server
|
|
|
|
// TraceServiceServer exposes the exporter server so that the auth server may
|
|
// collect and forward spans
|
|
collectortracepb.TraceServiceServer
|
|
|
|
// createAuditStreamSemaphore, if not nil, is used to limit the amount of
|
|
// in-flight CreateAuditStream RPCs, by sending a value in at the beginning
|
|
// of the RPC and pulling one out before returning.
|
|
createAuditStreamSemaphore chan struct{}
|
|
}
|
|
|
|
// Export forwards OTLP traces to the upstream collector configured in the tracing service. This allows for
|
|
// tsh, tctl, etc to be able to export traces without having to know how to connect to the upstream collector
|
|
// for the cluster.
|
|
func (g *GRPCServer) Export(ctx context.Context, req *collectortracepb.ExportTraceServiceRequest) (*collectortracepb.ExportTraceServiceResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if len(req.ResourceSpans) == 0 {
|
|
return &collectortracepb.ExportTraceServiceResponse{}, nil
|
|
}
|
|
|
|
return auth.Export(ctx, req)
|
|
}
|
|
|
|
// GetServer returns an instance of grpc server
|
|
func (g *GRPCServer) GetServer() (*grpc.Server, error) {
|
|
if g.server == nil {
|
|
return nil, trace.BadParameter("grpc server has not been initialized")
|
|
}
|
|
|
|
return g.server, nil
|
|
}
|
|
|
|
// EmitAuditEvent emits audit event
|
|
func (g *GRPCServer) EmitAuditEvent(ctx context.Context, req *apievents.OneOf) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
event, err := apievents.FromOneOf(*req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.EmitAuditEvent(ctx, event)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
var connectedResourceGauges = map[string]prometheus.Gauge{
|
|
constants.KeepAliveNode: connectedResources.WithLabelValues(constants.KeepAliveNode),
|
|
constants.KeepAliveKube: connectedResources.WithLabelValues(constants.KeepAliveKube),
|
|
constants.KeepAliveApp: connectedResources.WithLabelValues(constants.KeepAliveApp),
|
|
constants.KeepAliveDatabase: connectedResources.WithLabelValues(constants.KeepAliveDatabase),
|
|
constants.KeepAliveDatabaseService: connectedResources.WithLabelValues(constants.KeepAliveDatabaseService),
|
|
constants.KeepAliveWindowsDesktopService: connectedResources.WithLabelValues(constants.KeepAliveWindowsDesktopService),
|
|
}
|
|
|
|
// SendKeepAlives allows node to send a stream of keep alive requests
|
|
func (g *GRPCServer) SendKeepAlives(stream authpb.AuthService_SendKeepAlivesServer) error {
|
|
defer stream.SendAndClose(&emptypb.Empty{})
|
|
firstIteration := true
|
|
for {
|
|
// Authenticate within the loop to block locked-out nodes from heartbeating.
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
keepAlive, err := stream.Recv()
|
|
if errors.Is(err, io.EOF) {
|
|
g.logger.DebugContext(stream.Context(), "Connection closed")
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
g.logger.DebugContext(stream.Context(), "Failed to receive heartbeat", "error", err)
|
|
return trace.Wrap(err)
|
|
}
|
|
err = auth.KeepAliveServer(stream.Context(), *keepAlive)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if firstIteration {
|
|
g.logger.DebugContext(stream.Context(), "Got heartbeat connection",
|
|
"heartbeat_type", keepAlive.GetType(),
|
|
"identity", auth.User.GetName(),
|
|
)
|
|
heartbeatConnectionsReceived.Inc()
|
|
|
|
metric, ok := connectedResourceGauges[keepAlive.GetType()]
|
|
if ok {
|
|
metric.Inc()
|
|
defer metric.Dec()
|
|
} else {
|
|
g.logger.WarnContext(stream.Context(), "missing connected resources gauge for keep alive (this is a bug)", "heartbeat_type", keepAlive.GetType())
|
|
}
|
|
|
|
firstIteration = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// CreateAuditStream creates or resumes audit event stream
|
|
func (g *GRPCServer) CreateAuditStream(stream authpb.AuthService_CreateAuditStreamServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if sem := g.createAuditStreamSemaphore; sem != nil {
|
|
select {
|
|
case sem <- struct{}{}:
|
|
createAuditStreamAcceptedTotalMetric.Inc()
|
|
defer func() { <-sem }()
|
|
default:
|
|
createAuditStreamRejectedTotalMetric.Inc()
|
|
// [trace.ConnectionProblemError] is rendered with a gRPC
|
|
// "unavailable" error code, which is the correct error if the
|
|
// client can just back off and retry with no further changes to the
|
|
// request
|
|
return trace.ConnectionProblem(nil, "too many concurrent CreateAuditStream operations, try again later")
|
|
}
|
|
}
|
|
|
|
var eventStream apievents.Stream
|
|
var sessionID session.ID
|
|
g.logger.DebugContext(stream.Context(), "CreateAuditStream connection", "identity", auth.User.GetName())
|
|
streamStart := time.Now()
|
|
processed := int64(0)
|
|
counter := 0
|
|
forwardEvents := func(eventStream apievents.Stream) {
|
|
for {
|
|
select {
|
|
case <-stream.Context().Done():
|
|
return
|
|
case statusUpdate := <-eventStream.Status():
|
|
if err := stream.Send(&statusUpdate); err != nil {
|
|
g.logger.DebugContext(stream.Context(), "Failed to send status update", "error", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
closeStream := func(eventStream apievents.Stream) {
|
|
if err := eventStream.Close(auth.CloseContext()); err != nil {
|
|
if auth.CloseContext().Err() == nil {
|
|
g.logger.WarnContext(stream.Context(), "Failed to flush close the stream", "error", err)
|
|
}
|
|
} else {
|
|
g.logger.DebugContext(stream.Context(), "Flushed and closed the stream")
|
|
}
|
|
}
|
|
|
|
for {
|
|
request, err := stream.Recv()
|
|
if errors.Is(err, io.EOF) {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
if stream.Context().Err() == nil {
|
|
g.logger.DebugContext(stream.Context(), "Failed to receive stream request", "error", err)
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
if create := request.GetCreateStream(); create != nil {
|
|
if eventStream != nil {
|
|
return trace.BadParameter("stream is already created or resumed")
|
|
}
|
|
eventStream, err = auth.CreateAuditStream(stream.Context(), session.ID(create.SessionID))
|
|
if err != nil {
|
|
// Log the reason why audit stream creation failed. This will
|
|
// surface things like AWS/GCP/MinIO credential/configuration
|
|
// errors.
|
|
g.logger.ErrorContext(stream.Context(), "Failed to create audit stream", "error", err)
|
|
return trace.Wrap(err)
|
|
}
|
|
sessionID = session.ID(create.SessionID)
|
|
g.logger.DebugContext(stream.Context(), "Created stream for session", "session_id", sessionID)
|
|
go forwardEvents(eventStream)
|
|
defer closeStream(eventStream)
|
|
} else if resume := request.GetResumeStream(); resume != nil {
|
|
if eventStream != nil {
|
|
return trace.BadParameter("stream is already created or resumed")
|
|
}
|
|
eventStream, err = auth.ResumeAuditStream(stream.Context(), session.ID(resume.SessionID), resume.UploadID)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
g.logger.DebugContext(stream.Context(), "Resumed stream for session", "session_id", resume.SessionID)
|
|
go forwardEvents(eventStream)
|
|
defer closeStream(eventStream)
|
|
} else if complete := request.GetCompleteStream(); complete != nil {
|
|
if eventStream == nil {
|
|
return trace.BadParameter("stream is not initialized yet, cannot complete")
|
|
}
|
|
// do not use stream context to give the auth server finish the upload
|
|
// even if the stream's context is canceled
|
|
err := eventStream.Complete(auth.CloseContext())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
clusterName, err := auth.GetClusterName(auth.CloseContext())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if g.APIConfig.MetadataGetter != nil {
|
|
sessionData := g.APIConfig.MetadataGetter.GetUploadMetadata(sessionID)
|
|
// TODO(zmb3): this may result in duplicate upload events, as the upload
|
|
// completer will emit its own session.upload
|
|
event := &apievents.SessionUpload{
|
|
Metadata: apievents.Metadata{
|
|
Type: events.SessionUploadEvent,
|
|
Code: events.SessionUploadCode,
|
|
ID: uuid.New().String(),
|
|
Index: events.SessionUploadIndex,
|
|
ClusterName: clusterName.GetClusterName(),
|
|
},
|
|
SessionMetadata: apievents.SessionMetadata{
|
|
SessionID: string(sessionData.SessionID),
|
|
},
|
|
SessionURL: sessionData.URL,
|
|
}
|
|
if err := g.Emitter.EmitAuditEvent(auth.CloseContext(), event); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
g.logger.DebugContext(stream.Context(), "Completed stream for session", "session_id", sessionID)
|
|
return nil
|
|
} else if flushAndClose := request.GetFlushAndCloseStream(); flushAndClose != nil {
|
|
if eventStream == nil {
|
|
return trace.BadParameter("stream is not initialized yet, cannot flush and close")
|
|
}
|
|
// flush and close is always done
|
|
return nil
|
|
} else if oneof := request.GetEvent(); oneof != nil {
|
|
if eventStream == nil {
|
|
return trace.BadParameter("stream cannot receive an event without first being created or resumed")
|
|
}
|
|
event, err := apievents.FromOneOf(*oneof)
|
|
if err != nil {
|
|
g.logger.DebugContext(stream.Context(), "Failed to decode event", "error", err)
|
|
return trace.Wrap(err)
|
|
}
|
|
// Currently only api/client.auditStreamer calls with an event
|
|
// and it always sends an events.PreparedSessionEvent, so
|
|
// this event can safely be assumed to be prepared. Use a
|
|
// events.NoOpPreparer to simply convert the event.
|
|
setter := &events.NoOpPreparer{}
|
|
start := time.Now()
|
|
preparedEvent, _ := setter.PrepareSessionEvent(event)
|
|
var errors []error
|
|
errors = append(errors, eventStream.RecordEvent(stream.Context(), preparedEvent))
|
|
|
|
err = trace.NewAggregate(errors...)
|
|
if err != nil {
|
|
switch {
|
|
case events.IsPermanentEmitError(err):
|
|
g.logger.ErrorContext(stream.Context(), "Failed to EmitAuditEvent due to a permanent error, event wil be omitted",
|
|
slog.Any("error", err),
|
|
slog.Group("event",
|
|
slog.String("type", event.GetType()),
|
|
slog.String("code", event.GetCode()),
|
|
slog.String("id", event.GetID()),
|
|
slog.Int64("index", event.GetIndex()),
|
|
),
|
|
)
|
|
continue
|
|
default:
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
processed += int64(event.Size())
|
|
seconds := time.Since(streamStart) / time.Second
|
|
counter++
|
|
if counter%logInterval == 0 {
|
|
if seconds > 0 {
|
|
kbytes := float64(processed) / 1000
|
|
g.logger.DebugContext(stream.Context(), "Processed events", "event_count", counter, "tx_rate", kbytes/float64(seconds))
|
|
}
|
|
}
|
|
diff := time.Since(start)
|
|
if diff > 100*time.Millisecond {
|
|
g.logger.WarnContext(stream.Context(), "RecordEvent took longer than 100ms",
|
|
"event_type", event.GetType(),
|
|
"duration", time.Since(event.GetTime()),
|
|
)
|
|
}
|
|
} else {
|
|
g.logger.ErrorContext(stream.Context(), "Rejecting unsupported stream request", "request", request)
|
|
return trace.BadParameter("unsupported stream request")
|
|
}
|
|
}
|
|
}
|
|
|
|
// logInterval is used to log stats after this many events
|
|
const logInterval = 10000
|
|
|
|
// WatchEvents returns a new stream of cluster events
|
|
func (g *GRPCServer) WatchEvents(watch *authpb.Watch, stream authpb.AuthService_WatchEventsServer) (err error) {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return trace.Wrap(WatchEvents(watch, stream, auth.User.GetName(), auth))
|
|
}
|
|
|
|
// WatchEvent is a stream interface for sending events.
|
|
type WatchEvent interface {
|
|
Context() context.Context
|
|
Send(*authpb.Event) error
|
|
}
|
|
|
|
type Watcher interface {
|
|
NewStream(ctx context.Context, watch types.Watch) (stream.Stream[types.Event], error)
|
|
}
|
|
|
|
// WatchEvents watches for events and streams them to the provided stream.
|
|
func WatchEvents(watch *authpb.Watch, stream WatchEvent, componentName string, auth Watcher) error {
|
|
servicesWatch := types.Watch{
|
|
Name: componentName,
|
|
Kinds: watch.Kinds,
|
|
AllowPartialSuccess: watch.AllowPartialSuccess,
|
|
}
|
|
|
|
// KindNamespace is being removed but v17 agents will still try to include
|
|
// it in their cache and they will occasionally do a GetNamespace, so we
|
|
// pretend to support it as a resource kind here; it's sound to do so
|
|
// because there will never be any events coming, and the GetNamespace and
|
|
// GetNamespaces APIs return static data
|
|
//
|
|
// TODO(espadolini): remove in v19
|
|
var removedNamespaceWatch bool
|
|
filteredKinds := watch.Kinds[:0]
|
|
for _, k := range watch.Kinds {
|
|
if k.Kind == types.KindNamespace {
|
|
removedNamespaceWatch = true
|
|
continue
|
|
}
|
|
filteredKinds = append(filteredKinds, k)
|
|
}
|
|
watch.Kinds = filteredKinds
|
|
|
|
events, err := auth.NewStream(stream.Context(), servicesWatch)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
defer func() {
|
|
serr := events.Done()
|
|
if err == nil {
|
|
err = serr
|
|
}
|
|
}()
|
|
|
|
for events.Next() {
|
|
event := events.Item()
|
|
// TODO(espadolini): remove in v19
|
|
if removedNamespaceWatch {
|
|
if status, ok := event.Resource.(*types.WatchStatusV1); ok {
|
|
status.Spec.Kinds = append(status.Spec.Kinds, types.WatchKind{Kind: types.KindNamespace})
|
|
}
|
|
// there's only exactly one event of type OpInit and WatchStatus
|
|
// meta-resource (at the beginning of the stream), so we don't need
|
|
// to keep checking
|
|
removedNamespaceWatch = false
|
|
}
|
|
if role, ok := event.Resource.(*types.RoleV6); ok {
|
|
downgraded, err := maybeDowngradeRole(stream.Context(), role)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
event.Resource = downgraded
|
|
}
|
|
out, err := client.EventToGRPC(event)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
size := float64(proto.Size(out))
|
|
watcherEventsEmitted.WithLabelValues(resourceLabel(event)).Observe(size)
|
|
watcherEventSizes.Observe(size)
|
|
|
|
if err := stream.Send(out); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
// deferred cleanup func will inject stream error if needed
|
|
return nil
|
|
}
|
|
|
|
// resourceLabel returns the label for the provided types.Event
|
|
func resourceLabel(event types.Event) string {
|
|
if event.Resource == nil {
|
|
return event.Type.String()
|
|
}
|
|
|
|
sub := event.Resource.GetSubKind()
|
|
if sub == "" {
|
|
return fmt.Sprintf("/%s", event.Resource.GetKind())
|
|
}
|
|
|
|
return fmt.Sprintf("/%s/%s", event.Resource.GetKind(), sub)
|
|
}
|
|
|
|
func (g *GRPCServer) GenerateUserCerts(ctx context.Context, req *authpb.UserCertsRequest) (*authpb.Certs, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := validateUserCertsRequest(auth, req); err != nil {
|
|
g.logger.DebugContext(ctx, "Validation of user certs request failed", "error", err)
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if req.Purpose == authpb.UserCertsRequest_CERT_PURPOSE_SINGLE_USE_CERTS {
|
|
certs, err := g.generateUserSingleUseCerts(ctx, auth, req)
|
|
return certs, trace.Wrap(err)
|
|
}
|
|
|
|
certs, err := auth.ServerWithRoles.GenerateUserCerts(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return certs, nil
|
|
}
|
|
|
|
func validateUserCertsRequest(actx *grpcContext, req *authpb.UserCertsRequest) error {
|
|
switch req.Usage {
|
|
case authpb.UserCertsRequest_All:
|
|
if req.Purpose == authpb.UserCertsRequest_CERT_PURPOSE_SINGLE_USE_CERTS {
|
|
return trace.BadParameter("single-use certificates cannot be issued for all purposes")
|
|
}
|
|
case authpb.UserCertsRequest_App:
|
|
if req.RouteToApp.Name == "" {
|
|
return trace.BadParameter("missing app Name field in an app-only UserCertsRequest")
|
|
}
|
|
case authpb.UserCertsRequest_SSH:
|
|
if req.NodeName == "" {
|
|
return trace.BadParameter("missing NodeName field in a ssh-only UserCertsRequest")
|
|
}
|
|
case authpb.UserCertsRequest_Kubernetes:
|
|
if req.KubernetesCluster == "" {
|
|
return trace.BadParameter("missing KubernetesCluster field in a kubernetes-only UserCertsRequest")
|
|
}
|
|
case authpb.UserCertsRequest_Database:
|
|
if req.RouteToDatabase.ServiceName == "" {
|
|
return trace.BadParameter("missing ServiceName field in a database-only UserCertsRequest")
|
|
}
|
|
case authpb.UserCertsRequest_WindowsDesktop:
|
|
if req.RouteToWindowsDesktop.WindowsDesktop == "" {
|
|
return trace.BadParameter("missing WindowsDesktop field in a windows-desktop-only UserCertsRequest")
|
|
}
|
|
default:
|
|
return trace.BadParameter("unknown certificate Usage %q", req.Usage)
|
|
}
|
|
|
|
if req.Purpose != authpb.UserCertsRequest_CERT_PURPOSE_SINGLE_USE_CERTS {
|
|
return nil
|
|
}
|
|
|
|
if req.MFAResponse != nil {
|
|
if req.MFAResponse.GetTOTP() != nil {
|
|
return trace.BadParameter("per-session MFA is not satisfied by OTP devices")
|
|
}
|
|
}
|
|
|
|
// Single-use certs require current user.
|
|
if err := actx.currentUserAction(req.Username); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateUserSingleUseCerts issues single-use user certificates.
|
|
func (g *GRPCServer) generateUserSingleUseCerts(ctx context.Context, actx *grpcContext, req *authpb.UserCertsRequest) (*authpb.Certs, error) {
|
|
setUserSingleUseCertsTTL(actx, req)
|
|
|
|
// We don't do MFA requirement validations here.
|
|
// Callers are supposed to use either use
|
|
// CreateAuthenticateChallengeRequest.MFARequiredCheck or call IsMFARequired,
|
|
// as appropriate for their scenario.
|
|
//
|
|
// If the request has an MFAAuthenticateResponse, then the caller gets a cert
|
|
// with device extensions. Otherwise, they don't.
|
|
|
|
// Generate the cert
|
|
singleUseCert, err := userSingleUseCertsGenerate(
|
|
ctx,
|
|
actx,
|
|
*req)
|
|
if err != nil {
|
|
g.logger.WarnContext(ctx, "Failed to generate single-use cert", "error", err)
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return singleUseCert, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GenerateHostCerts(ctx context.Context, req *authpb.HostCertsRequest) (*authpb.Certs, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Pass along the remote address the request came from to the registration function.
|
|
p, ok := peer.FromContext(ctx)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unable to find peer")
|
|
}
|
|
req.RemoteAddr = p.Addr.String()
|
|
|
|
certs, err := auth.ServerWithRoles.GenerateHostCerts(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return certs, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GenerateOpenSSHCert(ctx context.Context, req *authpb.OpenSSHCertRequest) (*authpb.OpenSSHCert, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
cert, err := auth.ServerWithRoles.GenerateOpenSSHCert(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return cert, nil
|
|
}
|
|
|
|
// AssertSystemRole is used by agents to prove that they have a given system role when their credentials
|
|
// originate from multiple separate join tokens so that they can be issued an instance certificate that
|
|
// encompasses all of their capabilities. This method will be deprecated once we have a more comprehensive
|
|
// model for join token joining/replacement.
|
|
func (g *GRPCServer) AssertSystemRole(ctx context.Context, req *authpb.SystemRoleAssertion) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
if err := auth.AssertSystemRole(ctx, *req); err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// icsServicesToMetricName is a helper for translating service names to keepalive names for control-stream
|
|
// purposes. When new services switch to using control-stream based heartbeats, they should be added here.
|
|
var icsServiceToMetricName = map[types.SystemRole]string{
|
|
types.RoleNode: constants.KeepAliveNode,
|
|
types.RoleApp: constants.KeepAliveApp,
|
|
}
|
|
|
|
func (g *GRPCServer) InventoryControlStream(stream authpb.AuthService_InventoryControlStreamServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trail.ToGRPC(err)
|
|
}
|
|
|
|
p, ok := peer.FromContext(stream.Context())
|
|
if !ok {
|
|
return trace.BadParameter("unable to find peer")
|
|
}
|
|
|
|
ics := client.NewUpstreamInventoryControlStream(stream, p.Addr.String())
|
|
|
|
hello, err := auth.RegisterInventoryControlStream(ics)
|
|
if err != nil {
|
|
return trail.ToGRPC(err)
|
|
}
|
|
|
|
// we use a different name for a service in our metrics than we do in certs/hellos. the subset of
|
|
// services that currently use ics for heartbeats are registered in the icsServiceToMetricName
|
|
// mapping for translation.
|
|
var metricServices []string
|
|
for _, service := range hello.Services {
|
|
if name, ok := icsServiceToMetricName[service]; ok {
|
|
metricServices = append(metricServices, name)
|
|
}
|
|
}
|
|
|
|
// the heartbeatConnectionsReceived metric counts individual services as individual connections.
|
|
heartbeatConnectionsReceived.Add(float64(len(metricServices)))
|
|
|
|
// hold open the stream until it completes
|
|
<-ics.Done()
|
|
|
|
if errors.Is(ics.Error(), io.EOF) {
|
|
return nil
|
|
}
|
|
|
|
return trail.ToGRPC(ics.Error())
|
|
}
|
|
|
|
func (g *GRPCServer) GetInventoryStatus(ctx context.Context, req *authpb.InventoryStatusRequest) (*authpb.InventoryStatusSummary, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
rsp, err := auth.GetInventoryStatus(ctx, *req)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &rsp, nil
|
|
}
|
|
|
|
// GetInventoryConnectedServiceCounts returns the counts of each connected service seen in the inventory.
|
|
func (g *GRPCServer) GetInventoryConnectedServiceCounts(ctx context.Context, _ *authpb.InventoryConnectedServiceCountsRequest) (*authpb.InventoryConnectedServiceCounts, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
rsp, err := auth.GetInventoryConnectedServiceCounts()
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &rsp, nil
|
|
}
|
|
|
|
func (g *GRPCServer) PingInventory(ctx context.Context, req *authpb.InventoryPingRequest) (*authpb.InventoryPingResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
rsp, err := auth.PingInventory(ctx, *req)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &rsp, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetInstances(filter *types.InstanceFilter, stream authpb.AuthService_GetInstancesServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
instances := auth.GetInstances(stream.Context(), *filter)
|
|
|
|
for instances.Next() {
|
|
instance, ok := instances.Item().(*types.InstanceV1)
|
|
if !ok {
|
|
g.logger.WarnContext(stream.Context(), "Skipping unexpected instance type",
|
|
"instance_type", logutils.TypeAttr(instances.Item()),
|
|
"expected_instance_type", logutils.TypeAttr(instance),
|
|
)
|
|
continue
|
|
}
|
|
if err := stream.Send(instance); err != nil {
|
|
instances.Done()
|
|
if errors.Is(err, io.EOF) {
|
|
return nil
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
return trace.Wrap(instances.Done())
|
|
}
|
|
|
|
func (g *GRPCServer) GetClusterAlerts(ctx context.Context, query *types.GetClusterAlertsRequest) (*authpb.GetClusterAlertsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
alerts, err := auth.GetClusterAlerts(ctx, *query)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &authpb.GetClusterAlertsResponse{
|
|
Alerts: alerts,
|
|
}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) UpsertClusterAlert(ctx context.Context, req *authpb.UpsertClusterAlertRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
if err := auth.UpsertClusterAlert(ctx, req.Alert); err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) CreateAlertAck(ctx context.Context, ack *types.AlertAcknowledgement) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
if err := auth.CreateAlertAck(ctx, *ack); err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetAlertAcks(ctx context.Context, _ *authpb.GetAlertAcksRequest) (*authpb.GetAlertAcksResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
acks, err := auth.GetAlertAcks(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &authpb.GetAlertAcksResponse{
|
|
Acks: acks,
|
|
}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) ClearAlertAcks(ctx context.Context, req *authpb.ClearAlertAcksRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
if err := auth.ClearAlertAcks(ctx, *req); err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetCurrentUserRoles(_ *emptypb.Empty, stream authpb.AuthService_GetCurrentUserRolesServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
roles, err := auth.ServerWithRoles.GetCurrentUserRoles(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
for _, role := range roles {
|
|
v6, ok := role.(*types.RoleV6)
|
|
if !ok {
|
|
g.logger.WarnContext(stream.Context(), "expected type RoleV6, got unexpected for role type",
|
|
"role_type", logutils.TypeAttr(role),
|
|
"role", role.GetName(),
|
|
)
|
|
return trace.Errorf("encountered unexpected role type")
|
|
}
|
|
if err := stream.Send(v6); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetAccessRequestsV2(f *types.AccessRequestFilter, stream authpb.AuthService_GetAccessRequestsV2Server) error {
|
|
ctx := stream.Context()
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
var filter types.AccessRequestFilter
|
|
if f != nil {
|
|
filter = *f
|
|
}
|
|
reqs, err := auth.ServerWithRoles.GetAccessRequests(ctx, filter)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
for _, req := range reqs {
|
|
r, ok := req.(*types.AccessRequestV3)
|
|
if !ok {
|
|
err = trace.BadParameter("unexpected access request type %T", req)
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
if err := stream.Send(r); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *GRPCServer) ListAccessRequests(ctx context.Context, req *authpb.ListAccessRequestsRequest) (*authpb.ListAccessRequestsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rsp, err := auth.ServerWithRoles.ListAccessRequests(ctx, req)
|
|
|
|
return rsp, trace.Wrap(err)
|
|
}
|
|
|
|
func (g *GRPCServer) CreateAccessRequest(ctx context.Context, req *types.AccessRequestV3) (*emptypb.Empty, error) {
|
|
return nil, trace.NotImplemented("access request creation API has changed, please update your client to v14 or newer")
|
|
}
|
|
|
|
func (g *GRPCServer) CreateAccessRequestV2(ctx context.Context, req *types.AccessRequestV3) (*types.AccessRequestV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := services.ValidateAccessRequest(req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := services.ValidateAccessRequestClusterNames(g.AuthServer, req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
out, err := auth.ServerWithRoles.CreateAccessRequestV2(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
r, ok := out.(*types.AccessRequestV3)
|
|
if !ok {
|
|
return nil, trace.Wrap(trace.BadParameter("unexpected access request type %T", r))
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (g *GRPCServer) DeleteAccessRequest(ctx context.Context, id *authpb.RequestID) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteAccessRequest(ctx, id.ID); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) SetAccessRequestState(ctx context.Context, req *authpb.RequestStateSetter) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if req.Delegator != "" {
|
|
ctx = authz.WithDelegator(ctx, req.Delegator)
|
|
}
|
|
if err := auth.ServerWithRoles.SetAccessRequestState(ctx, types.AccessRequestUpdate{
|
|
RequestID: req.ID,
|
|
State: req.State,
|
|
Reason: req.Reason,
|
|
Annotations: req.Annotations,
|
|
Roles: req.Roles,
|
|
AssumeStartTime: req.AssumeStartTime,
|
|
}); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) SubmitAccessReview(ctx context.Context, review *types.AccessReviewSubmission) (*types.AccessRequestV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
req, err := auth.ServerWithRoles.SubmitAccessReview(ctx, *review)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
r, ok := req.(*types.AccessRequestV3)
|
|
if !ok {
|
|
err = trace.BadParameter("unexpected access request type %T", req)
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetAccessRequestAllowedPromotions(ctx context.Context, request *authpb.AccessRequestAllowedPromotionRequest) (*authpb.AccessRequestAllowedPromotionResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
accessRequest, err := auth.GetAccessRequests(ctx, types.AccessRequestFilter{
|
|
ID: request.AccessRequestID,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if len(accessRequest) != 1 {
|
|
return nil, trace.NotFound("access request not found")
|
|
}
|
|
|
|
allowedPromotions, err := auth.ServerWithRoles.GetAccessRequestAllowedPromotions(ctx, accessRequest[0])
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &authpb.AccessRequestAllowedPromotionResponse{
|
|
AllowedPromotions: allowedPromotions,
|
|
}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetAccessCapabilities(ctx context.Context, req *types.AccessCapabilitiesRequest) (*types.AccessCapabilities, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
caps, err := auth.ServerWithRoles.GetAccessCapabilities(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return caps, nil
|
|
}
|
|
|
|
func (g *GRPCServer) CreateResetPasswordToken(ctx context.Context, req *authpb.CreateResetPasswordTokenRequest) (*types.UserTokenV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if req == nil {
|
|
req = &authpb.CreateResetPasswordTokenRequest{}
|
|
}
|
|
|
|
token, err := auth.CreateResetPasswordToken(ctx, authclient.CreateUserTokenRequest{
|
|
Name: req.Name,
|
|
TTL: time.Duration(req.TTL),
|
|
Type: req.Type,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
r, ok := token.(*types.UserTokenV3)
|
|
if !ok {
|
|
err = trace.BadParameter("unexpected UserToken type %T", token)
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetResetPasswordToken(ctx context.Context, req *authpb.GetResetPasswordTokenRequest) (*types.UserTokenV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
tokenID := ""
|
|
if req != nil {
|
|
tokenID = req.TokenID
|
|
}
|
|
|
|
token, err := auth.GetResetPasswordToken(ctx, tokenID)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
r, ok := token.(*types.UserTokenV3)
|
|
if !ok {
|
|
err = trace.BadParameter("unexpected UserToken type %T", token)
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// GetPluginData loads all plugin data matching the supplied filter.
|
|
func (g *GRPCServer) GetPluginData(ctx context.Context, filter *types.PluginDataFilter) (*authpb.PluginDataSeq, error) {
|
|
// TODO(fspmarshall): Implement rate-limiting to prevent misbehaving plugins from
|
|
// consuming too many server resources.
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
data, err := auth.ServerWithRoles.GetPluginData(ctx, *filter)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var seq []*types.PluginDataV3
|
|
for _, rsc := range data {
|
|
d, ok := rsc.(*types.PluginDataV3)
|
|
if !ok {
|
|
err = trace.BadParameter("unexpected plugin data type %T", rsc)
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
seq = append(seq, d)
|
|
}
|
|
return &authpb.PluginDataSeq{
|
|
PluginData: seq,
|
|
}, nil
|
|
}
|
|
|
|
// UpdatePluginData updates a per-resource PluginData entry.
|
|
func (g *GRPCServer) UpdatePluginData(ctx context.Context, params *types.PluginDataUpdateParams) (*emptypb.Empty, error) {
|
|
// TODO(fspmarshall): Implement rate-limiting to prevent misbehaving plugins from
|
|
// consuming too many server resources.
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.UpdatePluginData(ctx, *params); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) Ping(ctx context.Context, req *authpb.PingRequest) (*authpb.PingResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
rsp, err := auth.Ping(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// attempt to set remote addr.
|
|
if p, ok := peer.FromContext(ctx); ok {
|
|
rsp.RemoteAddr = p.Addr.String()
|
|
}
|
|
|
|
return &rsp, nil
|
|
}
|
|
|
|
// AcquireSemaphore acquires lease with requested resources from semaphore.
|
|
func (g *GRPCServer) AcquireSemaphore(ctx context.Context, params *types.AcquireSemaphoreRequest) (*types.SemaphoreLease, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
lease, err := auth.AcquireSemaphore(ctx, *params)
|
|
return lease, trace.Wrap(err)
|
|
}
|
|
|
|
// KeepAliveSemaphoreLease updates semaphore lease.
|
|
func (g *GRPCServer) KeepAliveSemaphoreLease(ctx context.Context, req *types.SemaphoreLease) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.KeepAliveSemaphoreLease(ctx, *req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// CancelSemaphoreLease cancels semaphore lease early.
|
|
func (g *GRPCServer) CancelSemaphoreLease(ctx context.Context, req *types.SemaphoreLease) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.CancelSemaphoreLease(ctx, *req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetSemaphores returns a list of all semaphores matching the supplied filter.
|
|
func (g *GRPCServer) GetSemaphores(ctx context.Context, req *types.SemaphoreFilter) (*authpb.Semaphores, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
semaphores, err := auth.GetSemaphores(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
ss := make([]*types.SemaphoreV3, 0, len(semaphores))
|
|
for _, sem := range semaphores {
|
|
s, ok := sem.(*types.SemaphoreV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected semaphore type: %T", sem)
|
|
}
|
|
ss = append(ss, s)
|
|
}
|
|
return &authpb.Semaphores{
|
|
Semaphores: ss,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteSemaphore deletes a semaphore matching the supplied filter.
|
|
func (g *GRPCServer) DeleteSemaphore(ctx context.Context, req *types.SemaphoreFilter) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteSemaphore(ctx, *req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpsertDatabaseServer registers a new database proxy server.
|
|
func (g *GRPCServer) UpsertDatabaseServer(ctx context.Context, req *authpb.UpsertDatabaseServerRequest) (*types.KeepAlive, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
keepAlive, err := auth.UpsertDatabaseServer(ctx, req.GetServer())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return keepAlive, nil
|
|
}
|
|
|
|
// DeleteDatabaseServer removes the specified database proxy server.
|
|
func (g *GRPCServer) DeleteDatabaseServer(ctx context.Context, req *authpb.DeleteDatabaseServerRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteDatabaseServer(ctx, req.GetNamespace(), req.GetHostID(), req.GetName())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllDatabaseServers removes all registered database proxy servers.
|
|
func (g *GRPCServer) DeleteAllDatabaseServers(ctx context.Context, req *authpb.DeleteAllDatabaseServersRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteAllDatabaseServers(ctx, req.GetNamespace())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpsertDatabaseService registers a new database service.
|
|
func (g *GRPCServer) UpsertDatabaseService(ctx context.Context, req *authpb.UpsertDatabaseServiceRequest) (*types.KeepAlive, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
keepAlive, err := auth.UpsertDatabaseService(ctx, req.Service)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return keepAlive, nil
|
|
}
|
|
|
|
// DeleteDatabaseService removes the specified DatabaseService.
|
|
func (g *GRPCServer) DeleteDatabaseService(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
err = auth.DeleteDatabaseService(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllDatabaseServices removes all registered DatabaseServices.
|
|
func (g *GRPCServer) DeleteAllDatabaseServices(ctx context.Context, _ *authpb.DeleteAllDatabaseServicesRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
err = auth.DeleteAllDatabaseServices(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// SignDatabaseCSR generates a client certificate used by proxy when talking
|
|
// to a remote database service.
|
|
func (g *GRPCServer) SignDatabaseCSR(ctx context.Context, req *authpb.DatabaseCSRRequest) (*authpb.DatabaseCSRResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
response, err := auth.SignDatabaseCSR(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// GenerateDatabaseCert generates a client certificate used by a database
|
|
// service to authenticate with the database instance, or a server certificate
|
|
// for configuring a self-hosted database, depending on the requester_name.
|
|
func (g *GRPCServer) GenerateDatabaseCert(ctx context.Context, req *authpb.DatabaseCertRequest) (*authpb.DatabaseCertResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
response, err := auth.GenerateDatabaseCert(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// GenerateSnowflakeJWT generates JWT in the format required by Snowflake.
|
|
func (g *GRPCServer) GenerateSnowflakeJWT(ctx context.Context, req *authpb.SnowflakeJWTRequest) (*authpb.SnowflakeJWTResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
response, err := auth.GenerateSnowflakeJWT(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// UpsertApplicationServer registers an application server.
|
|
func (g *GRPCServer) UpsertApplicationServer(ctx context.Context, req *authpb.UpsertApplicationServerRequest) (*types.KeepAlive, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
server := req.GetServer()
|
|
app := server.GetApp()
|
|
|
|
// Only allow app servers with Okta origins if coming from an Okta role. App servers sourced from
|
|
// Okta are redirected differently which could create unpredictable or insecure behavior if applied
|
|
// to non-Okta apps.
|
|
hasOktaOrigin := server.Origin() == types.OriginOkta || app.Origin() == types.OriginOkta
|
|
if !authz.HasBuiltinRole(auth.context, string(types.RoleOkta)) {
|
|
if hasOktaOrigin {
|
|
return nil, trace.BadParameter("only the Okta role can create app servers and apps with an Okta origin")
|
|
}
|
|
}
|
|
|
|
keepAlive, err := auth.UpsertApplicationServer(ctx, server)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return keepAlive, nil
|
|
}
|
|
|
|
// DeleteApplicationServer deletes an application server.
|
|
func (g *GRPCServer) DeleteApplicationServer(ctx context.Context, req *authpb.DeleteApplicationServerRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteApplicationServer(ctx, req.GetNamespace(), req.GetHostID(), req.GetName())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllApplicationServers deletes all registered application servers.
|
|
func (g *GRPCServer) DeleteAllApplicationServers(ctx context.Context, req *authpb.DeleteAllApplicationServersRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteAllApplicationServers(ctx, req.GetNamespace())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetAppSession gets an application web session.
|
|
func (g *GRPCServer) GetAppSession(ctx context.Context, req *authpb.GetAppSessionRequest) (*authpb.GetAppSessionResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
session, err := auth.GetAppSession(ctx, types.GetAppSessionRequest{
|
|
SessionID: req.GetSessionID(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, ok := session.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected session type %T", session)
|
|
}
|
|
|
|
return &authpb.GetAppSessionResponse{
|
|
Session: sess,
|
|
}, nil
|
|
}
|
|
|
|
// ListAppSessions gets a paginated list of application web sessions.
|
|
func (g *GRPCServer) ListAppSessions(ctx context.Context, req *authpb.ListAppSessionsRequest) (*authpb.ListAppSessionsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
sessions, token, err := auth.ListAppSessions(ctx, int(req.PageSize), req.PageToken, req.User)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
out := make([]*types.WebSessionV2, 0, len(sessions))
|
|
for _, sess := range sessions {
|
|
s, ok := sess.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", sess)
|
|
}
|
|
out = append(out, s)
|
|
}
|
|
|
|
return &authpb.ListAppSessionsResponse{Sessions: out, NextPageToken: token}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetSnowflakeSession(ctx context.Context, req *authpb.GetSnowflakeSessionRequest) (*authpb.GetSnowflakeSessionResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
snowflakeSession, err := auth.GetSnowflakeSession(ctx, types.GetSnowflakeSessionRequest{SessionID: req.GetSessionID()})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, ok := snowflakeSession.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected session type %T", snowflakeSession)
|
|
}
|
|
|
|
return &authpb.GetSnowflakeSessionResponse{
|
|
Session: sess,
|
|
}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetSnowflakeSessions(ctx context.Context, e *emptypb.Empty) (*authpb.GetSnowflakeSessionsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
sessions, err := auth.GetSnowflakeSessions(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
var out []*types.WebSessionV2
|
|
for _, session := range sessions {
|
|
sess, ok := session.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", session)
|
|
}
|
|
out = append(out, sess)
|
|
}
|
|
|
|
return &authpb.GetSnowflakeSessionsResponse{
|
|
Sessions: out,
|
|
}, nil
|
|
}
|
|
|
|
// GetSAMLIdPSession gets a SAML IdPsession.
|
|
// TODO(Joerger): DELETE IN v18.0.0
|
|
func (g *GRPCServer) GetSAMLIdPSession(ctx context.Context, req *authpb.GetSAMLIdPSessionRequest) (*authpb.GetSAMLIdPSessionResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
samlSession, err := auth.GetSAMLIdPSession(ctx, types.GetSAMLIdPSessionRequest{SessionID: req.GetSessionID()})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, ok := samlSession.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected session type %T", samlSession)
|
|
}
|
|
|
|
return &authpb.GetSAMLIdPSessionResponse{
|
|
Session: sess,
|
|
}, nil
|
|
}
|
|
|
|
// ListSAMLIdPSessions gets a paginated list of SAML IdP sessions.
|
|
// TODO(Joerger): DELETE IN v18.0.0
|
|
func (g *GRPCServer) ListSAMLIdPSessions(ctx context.Context, req *authpb.ListSAMLIdPSessionsRequest) (*authpb.ListSAMLIdPSessionsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
sessions, token, err := auth.ListSAMLIdPSessions(ctx, int(req.PageSize), req.PageToken, req.User)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
out := make([]*types.WebSessionV2, 0, len(sessions))
|
|
for _, sess := range sessions {
|
|
s, ok := sess.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", sess)
|
|
}
|
|
out = append(out, s)
|
|
}
|
|
|
|
return &authpb.ListSAMLIdPSessionsResponse{Sessions: out, NextPageToken: token}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) DeleteSnowflakeSession(ctx context.Context, req *authpb.DeleteSnowflakeSessionRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteSnowflakeSession(ctx, types.DeleteSnowflakeSessionRequest{
|
|
SessionID: req.GetSessionID(),
|
|
}); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) DeleteAllSnowflakeSessions(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteAllSnowflakeSessions(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// CreateAppSession creates an application web session. Application web
|
|
// sessions represent a browser session the client holds.
|
|
func (g *GRPCServer) CreateAppSession(ctx context.Context, req *authpb.CreateAppSessionRequest) (*authpb.CreateAppSessionResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
session, err := auth.CreateAppSession(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, ok := session.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", session)
|
|
}
|
|
|
|
return &authpb.CreateAppSessionResponse{
|
|
Session: sess,
|
|
}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) CreateSnowflakeSession(ctx context.Context, req *authpb.CreateSnowflakeSessionRequest) (*authpb.CreateSnowflakeSessionResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
snowflakeSession, err := auth.CreateSnowflakeSession(ctx, types.CreateSnowflakeSessionRequest{
|
|
Username: req.GetUsername(),
|
|
SessionToken: req.GetSessionToken(),
|
|
TokenTTL: time.Duration(req.TokenTTL),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, ok := snowflakeSession.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", snowflakeSession)
|
|
}
|
|
|
|
return &authpb.CreateSnowflakeSessionResponse{
|
|
Session: sess,
|
|
}, nil
|
|
}
|
|
|
|
// CreateSAMLIdPSession creates a SAML IdP session.
|
|
// TODO(Joerger): DELETE IN v18.0.0
|
|
func (g *GRPCServer) CreateSAMLIdPSession(ctx context.Context, req *authpb.CreateSAMLIdPSessionRequest) (*authpb.CreateSAMLIdPSessionResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
session, err := auth.CreateSAMLIdPSession(ctx, types.CreateSAMLIdPSessionRequest{
|
|
SessionID: req.GetSessionID(),
|
|
Username: req.GetUsername(),
|
|
SAMLSession: req.GetSAMLSession(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, ok := session.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", session)
|
|
}
|
|
|
|
return &authpb.CreateSAMLIdPSessionResponse{
|
|
Session: sess,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteAppSession removes an application web session.
|
|
func (g *GRPCServer) DeleteAppSession(ctx context.Context, req *authpb.DeleteAppSessionRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteAppSession(ctx, types.DeleteAppSessionRequest{
|
|
SessionID: req.GetSessionID(),
|
|
}); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllAppSessions removes all application web sessions.
|
|
func (g *GRPCServer) DeleteAllAppSessions(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteAllAppSessions(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteUserAppSessions removes user's all application web sessions.
|
|
func (g *GRPCServer) DeleteUserAppSessions(ctx context.Context, req *authpb.DeleteUserAppSessionsRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteUserAppSessions(ctx, req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteSAMLIdPSession removes a SAML IdP session.
|
|
// TODO(Joerger): DELETE IN v18.0.0
|
|
func (g *GRPCServer) DeleteSAMLIdPSession(ctx context.Context, req *authpb.DeleteSAMLIdPSessionRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteSAMLIdPSession(ctx, types.DeleteSAMLIdPSessionRequest{
|
|
SessionID: req.GetSessionID(),
|
|
}); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllSAMLIdPSessions removes all SAML IdP sessions.
|
|
// TODO(Joerger): DELETE IN v18.0.0
|
|
func (g *GRPCServer) DeleteAllSAMLIdPSessions(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteAllSAMLIdPSessions(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteUserSAMLIdPSessions removes all of a user's SAML IdP sessions.
|
|
// TODO(Joerger): DELETE IN v18.0.0
|
|
func (g *GRPCServer) DeleteUserSAMLIdPSessions(ctx context.Context, req *authpb.DeleteUserSAMLIdPSessionsRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteUserSAMLIdPSessions(ctx, req.Username); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GenerateAppToken creates a JWT token with application access.
|
|
func (g *GRPCServer) GenerateAppToken(ctx context.Context, req *authpb.GenerateAppTokenRequest) (*authpb.GenerateAppTokenResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
traits := wrappers.Traits{}
|
|
for traitName, traitValues := range req.Traits {
|
|
traits[traitName] = traitValues.Values
|
|
}
|
|
token, err := auth.GenerateAppToken(ctx, types.GenerateAppTokenRequest{
|
|
Username: req.Username,
|
|
Roles: req.Roles,
|
|
Traits: traits,
|
|
URI: req.URI,
|
|
Expires: req.Expires,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &authpb.GenerateAppTokenResponse{
|
|
Token: token,
|
|
}, nil
|
|
}
|
|
|
|
// GetWebSession gets a web session.
|
|
func (g *GRPCServer) GetWebSession(ctx context.Context, req *types.GetWebSessionRequest) (*authpb.GetWebSessionResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
session, err := auth.WebSessions().Get(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sess, ok := session.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected session type %T", session)
|
|
}
|
|
|
|
return &authpb.GetWebSessionResponse{
|
|
Session: sess,
|
|
}, nil
|
|
}
|
|
|
|
// GetWebSessions gets all web sessions.
|
|
func (g *GRPCServer) GetWebSessions(ctx context.Context, _ *emptypb.Empty) (*authpb.GetWebSessionsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
sessions, err := auth.WebSessions().List(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
var out []*types.WebSessionV2
|
|
for _, session := range sessions {
|
|
sess, ok := session.(*types.WebSessionV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", session)
|
|
}
|
|
out = append(out, sess)
|
|
}
|
|
|
|
return &authpb.GetWebSessionsResponse{
|
|
Sessions: out,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteWebSession removes the web session given with req.
|
|
func (g *GRPCServer) DeleteWebSession(ctx context.Context, req *types.DeleteWebSessionRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.WebSessions().Delete(ctx, *req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllWebSessions removes all web sessions.
|
|
func (g *GRPCServer) DeleteAllWebSessions(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.WebSessions().DeleteAll(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetWebToken gets a web token.
|
|
func (g *GRPCServer) GetWebToken(ctx context.Context, req *types.GetWebTokenRequest) (*authpb.GetWebTokenResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := auth.WebTokens().Get(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
token, ok := resp.(*types.WebTokenV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected web token type %T", resp)
|
|
}
|
|
|
|
return &authpb.GetWebTokenResponse{
|
|
Token: token,
|
|
}, nil
|
|
}
|
|
|
|
// GetWebTokens gets all web tokens.
|
|
func (g *GRPCServer) GetWebTokens(ctx context.Context, _ *emptypb.Empty) (*authpb.GetWebTokensResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
tokens, err := auth.WebTokens().List(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
var out []*types.WebTokenV3
|
|
for _, t := range tokens {
|
|
token, ok := t.(*types.WebTokenV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", t)
|
|
}
|
|
out = append(out, token)
|
|
}
|
|
|
|
return &authpb.GetWebTokensResponse{
|
|
Tokens: out,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteWebToken removes the web token given with req.
|
|
func (g *GRPCServer) DeleteWebToken(ctx context.Context, req *types.DeleteWebTokenRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.WebTokens().Delete(ctx, *req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllWebTokens removes all web tokens.
|
|
func (g *GRPCServer) DeleteAllWebTokens(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.WebTokens().DeleteAll(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpsertKubernetesServer registers an kubernetes server.
|
|
func (g *GRPCServer) UpsertKubernetesServer(ctx context.Context, req *authpb.UpsertKubernetesServerRequest) (*types.KeepAlive, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
keepAlive, err := auth.UpsertKubernetesServer(ctx, req.GetServer())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return keepAlive, nil
|
|
}
|
|
|
|
// DeleteKubernetesServer deletes a kubernetes server.
|
|
func (g *GRPCServer) DeleteKubernetesServer(ctx context.Context, req *authpb.DeleteKubernetesServerRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteKubernetesServer(ctx, req.GetHostID(), req.GetName())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllKubernetesServers deletes all registered kubernetes servers.
|
|
func (g *GRPCServer) DeleteAllKubernetesServers(ctx context.Context, req *authpb.DeleteAllKubernetesServersRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteAllKubernetesServers(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// maybeDowngradeRole tests the client version passed through the gRPC metadata,
|
|
// and if the client version is unknown or less than the minimum supported
|
|
// version for some features of the role returns a shallow copy of the given
|
|
// role downgraded for compatibility with the older version.
|
|
func maybeDowngradeRole(ctx context.Context, role *types.RoleV6) (*types.RoleV6, error) {
|
|
clientVersionString, ok := metadata.ClientVersionFromContext(ctx)
|
|
if !ok {
|
|
// This client is not reporting its version via gRPC metadata. Teleport
|
|
// clients have been reporting their version for long enough that older
|
|
// clients won't even support v6 roles at all, so this is likely a
|
|
// third-party client, and we shouldn't assume that downgrading the role
|
|
// will do more good than harm.
|
|
return role, nil
|
|
}
|
|
|
|
clientVersion, err := semver.NewVersion(clientVersionString)
|
|
if err != nil {
|
|
return nil, trace.BadParameter("unrecognized client version: %s is not a valid semver", clientVersionString)
|
|
}
|
|
|
|
role = maybeDowngradeRoleSSHPortForwarding(role, clientVersion)
|
|
return role, nil
|
|
}
|
|
|
|
var minSupportedSSHPortForwardingVersion = semver.Version{Major: 17, Minor: 1, Patch: 0}
|
|
|
|
func maybeDowngradeRoleSSHPortForwarding(role *types.RoleV6, clientVersion *semver.Version) *types.RoleV6 {
|
|
sshPortForwarding := role.GetOptions().SSHPortForwarding
|
|
if sshPortForwarding == nil || (sshPortForwarding.Remote == nil && sshPortForwarding.Local == nil) {
|
|
return role
|
|
}
|
|
|
|
if supported, err := utils.MinVerWithoutPreRelease(
|
|
clientVersion.String(),
|
|
minSupportedSSHPortForwardingVersion.String()); supported || err != nil {
|
|
return role
|
|
}
|
|
|
|
role = apiutils.CloneProtoMsg(role)
|
|
options := role.GetOptions()
|
|
|
|
//nolint:staticcheck // this field is preserved for backwards compatibility
|
|
options.PortForwarding = types.NewBoolOption(services.RoleSet{role}.CanPortForward())
|
|
role.SetOptions(options)
|
|
reason := fmt.Sprintf(`Client version %q does not support granular SSH port forwarding. Role %q will be downgraded `+
|
|
`to simple port forwarding rules instead. In order to support granular SSH port forwarding, all clients must be `+
|
|
`updated to version %q or higher.`, clientVersion, role.GetName(), minSupportedSSHPortForwardingVersion)
|
|
if role.Metadata.Labels == nil {
|
|
role.Metadata.Labels = make(map[string]string, 1)
|
|
}
|
|
role.Metadata.Labels[types.TeleportDowngradedLabel] = reason
|
|
return role
|
|
}
|
|
|
|
// GetRole retrieves a role by name.
|
|
func (g *GRPCServer) GetRole(ctx context.Context, req *authpb.GetRoleRequest) (*types.RoleV6, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
roleI, err := auth.ServerWithRoles.GetRole(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
role, ok := roleI.(*types.RoleV6)
|
|
if !ok {
|
|
return nil, trace.Errorf("encountered unexpected role type: %T", role)
|
|
}
|
|
|
|
downgraded, err := maybeDowngradeRole(ctx, role)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return downgraded, nil
|
|
}
|
|
|
|
// GetRoles retrieves all roles.
|
|
func (g *GRPCServer) GetRoles(ctx context.Context, _ *emptypb.Empty) (*authpb.GetRolesResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
rolesI, err := auth.ServerWithRoles.GetRoles(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var roles []*types.RoleV6
|
|
for _, r := range rolesI {
|
|
role, ok := r.(*types.RoleV6)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", r)
|
|
}
|
|
downgraded, err := maybeDowngradeRole(ctx, role)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
roles = append(roles, downgraded)
|
|
}
|
|
return &authpb.GetRolesResponse{
|
|
Roles: roles,
|
|
}, nil
|
|
}
|
|
|
|
// ListRoles is a paginated role getter.
|
|
func (g *GRPCServer) ListRoles(ctx context.Context, req *authpb.ListRolesRequest) (*authpb.ListRolesResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rsp, err := auth.ServerWithRoles.ListRoles(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
downgradedRoles := rsp.Roles[:0]
|
|
for _, role := range rsp.Roles {
|
|
downgraded, err := maybeDowngradeRole(ctx, role)
|
|
if err != nil {
|
|
g.logger.WarnContext(ctx, "Failed to downgrade role, this is a bug and may result in spurious access denied errors",
|
|
"role", role.GetName(),
|
|
"error", err,
|
|
)
|
|
continue
|
|
}
|
|
downgradedRoles = append(downgradedRoles, downgraded)
|
|
}
|
|
rsp.Roles = downgradedRoles
|
|
|
|
return rsp, nil
|
|
}
|
|
|
|
// CreateRole creates a new role.
|
|
func (g *GRPCServer) CreateRole(ctx context.Context, req *authpb.CreateRoleRequest) (*types.RoleV6, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// This check *must* happen at the RPC layer rather than somewhere like ValidateRole or CheckAndSetDefaults. We want to prevent role
|
|
// creation and updates from defining both port_forwarding and ssh_port_forwarding for the same role. However, when making effective
|
|
// roles available to nodes it should be possible for both fields to be assigned in order to maintain backwards compatibility with older
|
|
// agents (similar to a role downgrade).
|
|
//nolint:staticcheck // this field is preserved for backwards compatibility, but shouldn't be used going forward
|
|
if req.Role.GetOptions().SSHPortForwarding != nil && req.Role.GetOptions().PortForwarding != nil {
|
|
return nil, trace.BadParameter("options define both 'port_forwarding' and 'ssh_port_forwarding', only one can be set")
|
|
}
|
|
|
|
if err = services.ValidateRole(req.Role); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
created, err := auth.ServerWithRoles.CreateRole(ctx, req.Role)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
g.logger.DebugContext(ctx, "role upserted", "role_name", req.Role.GetName())
|
|
|
|
v6, ok := created.(*types.RoleV6)
|
|
if !ok {
|
|
g.logger.WarnContext(ctx, "expected type RoleV6, got unexpected type",
|
|
"role_type", logutils.TypeAttr(created),
|
|
"role", created.GetName(),
|
|
)
|
|
return nil, trace.BadParameter("encountered unexpected role type")
|
|
}
|
|
|
|
return v6, nil
|
|
}
|
|
|
|
// UpdateRole updates an existing role.
|
|
func (g *GRPCServer) UpdateRole(ctx context.Context, req *authpb.UpdateRoleRequest) (*types.RoleV6, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// This check *must* happen at the RPC layer rather than somewhere like ValidateRole or CheckAndSetDefaults. We want to prevent role
|
|
// creation and updates from defining both port_forwarding and ssh_port_forwarding for the same role. However, when making effective
|
|
// roles available to nodes it should be possible for both fields to be assigned in order to maintain backwards compatibility with older
|
|
// agents (similar to a role downgrade).
|
|
//nolint:staticcheck // this field is preserved for backwards compatibility, but shouldn't be used going forward
|
|
if req.Role.GetOptions().SSHPortForwarding != nil && req.Role.GetOptions().PortForwarding != nil {
|
|
return nil, trace.BadParameter("options define both 'port_forwarding' and 'ssh_port_forwarding', only one can be set")
|
|
}
|
|
|
|
if err = services.ValidateRole(req.Role); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
updated, err := auth.ServerWithRoles.UpdateRole(ctx, req.Role)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
g.logger.DebugContext(ctx, "role upserted", "role", req.Role.GetName())
|
|
|
|
v6, ok := updated.(*types.RoleV6)
|
|
if !ok {
|
|
g.logger.WarnContext(ctx, "expected type RoleV6, got unexpected type",
|
|
"role_type", logutils.TypeAttr(updated),
|
|
"role", updated.GetName(),
|
|
)
|
|
return nil, trace.BadParameter("encountered unexpected role type")
|
|
}
|
|
|
|
return v6, nil
|
|
}
|
|
|
|
// UpsertRoleV2 upserts a role.
|
|
func (g *GRPCServer) UpsertRoleV2(ctx context.Context, req *authpb.UpsertRoleRequest) (*types.RoleV6, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// This check *must* happen at the RPC layer rather than somewhere like ValidateRole or CheckAndSetDefaults. We want to prevent role
|
|
// creation and updates from defining both port_forwarding and ssh_port_forwarding for the same role. However, when making effective
|
|
// roles available to nodes it should be possible for both fields to be assigned in order to maintain backwards compatibility with older
|
|
// agents (similar to a role downgrade).
|
|
//nolint:staticcheck // this field is preserved for backwards compatibility, but shouldn't be used going forward
|
|
if req.Role.GetOptions().SSHPortForwarding != nil && req.Role.GetOptions().PortForwarding != nil {
|
|
return nil, trace.BadParameter("options define both 'port_forwarding' and 'ssh_port_forwarding', only one can be set")
|
|
}
|
|
|
|
if err = services.ValidateRole(req.Role); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
upserted, err := auth.ServerWithRoles.UpsertRole(ctx, req.Role)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
g.logger.DebugContext(ctx, "role upserted", "role", req.Role.GetName())
|
|
|
|
v6, ok := upserted.(*types.RoleV6)
|
|
if !ok {
|
|
g.logger.WarnContext(ctx, "expected type RoleV6, got unexpected type",
|
|
"role_type", logutils.TypeAttr(upserted),
|
|
"role", upserted.GetName(),
|
|
)
|
|
return nil, trace.BadParameter("encountered unexpected role type")
|
|
}
|
|
|
|
return v6, nil
|
|
}
|
|
|
|
// UpsertRole upserts a role.
|
|
func (g *GRPCServer) UpsertRole(ctx context.Context, role *types.RoleV6) (*emptypb.Empty, error) {
|
|
_, err := g.UpsertRoleV2(ctx, &authpb.UpsertRoleRequest{Role: role})
|
|
return &emptypb.Empty{}, trace.Wrap(err)
|
|
}
|
|
|
|
// DeleteRole deletes a role by name.
|
|
func (g *GRPCServer) DeleteRole(ctx context.Context, req *authpb.DeleteRoleRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteRole(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
g.logger.DebugContext(ctx, "role deleted", "role", req.GetName())
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// doMFAPresenceChallenge conducts an MFA presence challenge over a stream
|
|
// and updates the users presence for a given session.
|
|
//
|
|
// This function bypasses the `ServerWithRoles` RBAC layer. This is not
|
|
// usually how the gRPC layer accesses the underlying auth server API's but it's done
|
|
// here to avoid bloating the [authclient.ClientI] interface with special logic that isn't designed to be touched
|
|
// by anyone external to this process. This is not the norm and caution should be taken
|
|
// when looking at or modifying this function. This is the same approach taken by other MFA
|
|
// related gRPC API endpoints.
|
|
func doMFAPresenceChallenge(ctx context.Context, actx *grpcContext, stream authpb.AuthService_MaintainSessionPresenceServer, challengeReq *authpb.PresenceMFAChallengeRequest) error {
|
|
user := actx.User.GetName()
|
|
chalExt := &mfav1pb.ChallengeExtensions{Scope: mfav1pb.ChallengeScope_CHALLENGE_SCOPE_USER_SESSION}
|
|
authChallenge, err := actx.authServer.mfaAuthChallenge(ctx, user, challengeReq.SSOClientRedirectURL, challengeReq.ProxyAddress, chalExt)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
if authChallenge.WebauthnChallenge == nil {
|
|
return trace.BadParameter("no MFA devices registered for %q", user)
|
|
}
|
|
|
|
if err := stream.Send(authChallenge); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := stream.Recv()
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
challengeResp := resp.GetChallengeResponse()
|
|
if challengeResp == nil {
|
|
return trace.BadParameter("expected MFAAuthenticateResponse, got %T", challengeResp)
|
|
}
|
|
|
|
if _, err := actx.authServer.ValidateMFAAuthResponse(ctx, challengeResp, user, chalExt); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
err = actx.authServer.UpdatePresence(ctx, challengeReq.SessionID, user)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MaintainSessionPresence establishes a channel used to continuously verify the presence for a session.
|
|
func (g *GRPCServer) MaintainSessionPresence(stream authpb.AuthService_MaintainSessionPresenceServer) error {
|
|
ctx := stream.Context()
|
|
actx, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
for {
|
|
req, err := stream.Recv()
|
|
if errors.Is(err, io.EOF) {
|
|
return nil
|
|
}
|
|
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
challengeReq := req.GetChallengeRequest()
|
|
if challengeReq == nil {
|
|
return trace.BadParameter("expected PresenceMFAChallengeRequest, got %T", req)
|
|
}
|
|
|
|
err = doMFAPresenceChallenge(ctx, actx, stream, challengeReq)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Deprecated: Use AddMFADeviceSync instead.
|
|
func (g *GRPCServer) AddMFADevice(stream authpb.AuthService_AddMFADeviceServer) error {
|
|
return trace.NotImplemented("method AddMFADevice is deprecated, use AddMFADeviceSync instead")
|
|
}
|
|
|
|
// Deprecated: Use DeleteMFADeviceSync instead.
|
|
func (g *GRPCServer) DeleteMFADevice(stream authpb.AuthService_DeleteMFADeviceServer) error {
|
|
return trace.NotImplemented("method DeleteMFADevice is deprecated, use DeleteMFADeviceSync instead")
|
|
}
|
|
|
|
func mfaDeviceEventMetadata(d *types.MFADevice) apievents.MFADeviceMetadata {
|
|
return apievents.MFADeviceMetadata{
|
|
DeviceName: d.Metadata.Name,
|
|
DeviceID: d.Id,
|
|
DeviceType: d.MFAType(),
|
|
}
|
|
}
|
|
|
|
// AddMFADeviceSync is implemented by AuthService.AddMFADeviceSync.
|
|
func (g *GRPCServer) AddMFADeviceSync(ctx context.Context, req *authpb.AddMFADeviceSyncRequest) (*authpb.AddMFADeviceSyncResponse, error) {
|
|
actx, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
res, err := actx.ServerWithRoles.AddMFADeviceSync(ctx, req)
|
|
return res, trace.Wrap(err)
|
|
}
|
|
|
|
// DeleteMFADeviceSync is implemented by AuthService.DeleteMFADeviceSync.
|
|
func (g *GRPCServer) DeleteMFADeviceSync(ctx context.Context, req *authpb.DeleteMFADeviceSyncRequest) (*emptypb.Empty, error) {
|
|
actx, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := actx.ServerWithRoles.DeleteMFADeviceSync(ctx, req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetMFADevices(ctx context.Context, req *authpb.GetMFADevicesRequest) (*authpb.GetMFADevicesResponse, error) {
|
|
actx, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
devs, err := actx.ServerWithRoles.GetMFADevices(ctx, req)
|
|
return devs, trace.Wrap(err)
|
|
}
|
|
|
|
// Deprecated: Use GenerateUserCerts instead.
|
|
func (g *GRPCServer) GenerateUserSingleUseCerts(stream authpb.AuthService_GenerateUserSingleUseCertsServer) error {
|
|
return trace.NotImplemented("method GenerateUserSingleUseCerts is deprecated, use GenerateUserCerts instead")
|
|
}
|
|
|
|
func setUserSingleUseCertsTTL(actx *grpcContext, req *authpb.UserCertsRequest) {
|
|
if isLocalProxyCertReq(req) {
|
|
// don't limit the cert expiry to 1 minute for db local proxy tunnel or kube local proxy,
|
|
// because the certs will be kept in-memory by the client to protect
|
|
// against cert/key exfiltration. When MFA is required, cert expiration
|
|
// time is bounded by the lifetime of the local proxy process or the mfa verification interval.
|
|
return
|
|
}
|
|
|
|
maxExpiry := actx.authServer.GetClock().Now().Add(teleport.UserSingleUseCertTTL)
|
|
if req.Expires.After(maxExpiry) {
|
|
req.Expires = maxExpiry
|
|
}
|
|
}
|
|
|
|
// isLocalProxyCertReq returns whether a cert request is for a local proxy cert.
|
|
func isLocalProxyCertReq(req *authpb.UserCertsRequest) bool {
|
|
return (req.Usage == authpb.UserCertsRequest_Database &&
|
|
req.RequesterName == authpb.UserCertsRequest_TSH_DB_LOCAL_PROXY_TUNNEL) ||
|
|
(req.Usage == authpb.UserCertsRequest_Kubernetes &&
|
|
(req.RequesterName == authpb.UserCertsRequest_TSH_KUBE_LOCAL_PROXY || req.RequesterName == authpb.UserCertsRequest_TSH_KUBE_LOCAL_PROXY_HEADLESS)) ||
|
|
(req.Usage == authpb.UserCertsRequest_App &&
|
|
req.RequesterName == authpb.UserCertsRequest_TSH_APP_LOCAL_PROXY)
|
|
}
|
|
|
|
func userSingleUseCertsGenerate(ctx context.Context, actx *grpcContext, req authpb.UserCertsRequest) (*authpb.Certs, error) {
|
|
// Get the client IP.
|
|
clientPeer, ok := peer.FromContext(ctx)
|
|
if !ok {
|
|
return nil, trace.BadParameter("no peer info in gRPC stream, can't get client IP")
|
|
}
|
|
clientIP, _, err := net.SplitHostPort(clientPeer.Addr.String())
|
|
if err != nil {
|
|
return nil, trace.BadParameter("can't parse client IP from peer info: %v", err)
|
|
}
|
|
|
|
// MFA certificates are supposed to be always pinned to IP, but it was decided to turn this off until
|
|
// IP pinning comes out of preview. Here we would add option to pin the cert, see commit of this comment for restoring.
|
|
opts := []certRequestOption{
|
|
certRequestPreviousIdentityExpires(actx.Identity.GetIdentity().Expires),
|
|
certRequestLoginIP(clientIP),
|
|
certRequestDeviceExtensions(actx.Identity.GetIdentity().DeviceExtensions),
|
|
}
|
|
|
|
// Generate the cert.
|
|
certs, err := actx.generateUserCerts(ctx, req, opts...)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Defensively forward only the expected certificate, according to the
|
|
// requested usage.
|
|
resp := &authpb.Certs{}
|
|
switch req.Usage {
|
|
case authpb.UserCertsRequest_SSH:
|
|
resp.SSH = certs.SSH
|
|
case authpb.UserCertsRequest_Kubernetes, authpb.UserCertsRequest_Database, authpb.UserCertsRequest_WindowsDesktop, authpb.UserCertsRequest_App:
|
|
resp.TLS = certs.TLS
|
|
default:
|
|
return nil, trace.BadParameter("unknown certificate usage %q", req.Usage)
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func (g *GRPCServer) IsMFARequired(ctx context.Context, req *authpb.IsMFARequiredRequest) (*authpb.IsMFARequiredResponse, error) {
|
|
actx, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
resp, err := actx.IsMFARequired(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// GetOIDCConnector retrieves an OIDC connector by name.
|
|
func (g *GRPCServer) GetOIDCConnector(ctx context.Context, req *types.ResourceWithSecretsRequest) (*types.OIDCConnectorV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
oc, err := auth.ServerWithRoles.GetOIDCConnector(ctx, req.Name, req.WithSecrets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
connector, ok := oc.(*types.OIDCConnectorV3)
|
|
if !ok {
|
|
return nil, trace.Errorf("encountered unexpected OIDC connector type %T", oc)
|
|
}
|
|
return connector, nil
|
|
}
|
|
|
|
// GetOIDCConnectors retrieves valid OIDC connectors, errors from individual connectors are not forwarded.
|
|
func (g *GRPCServer) GetOIDCConnectors(ctx context.Context, req *types.ResourcesWithSecretsRequest) (*types.OIDCConnectorV3List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
ocs, err := auth.ServerWithRoles.GetOIDCConnectors(ctx, req.WithSecrets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
connectors := make([]*types.OIDCConnectorV3, len(ocs))
|
|
for i, oc := range ocs {
|
|
var ok bool
|
|
if connectors[i], ok = oc.(*types.OIDCConnectorV3); !ok {
|
|
return nil, trace.Errorf("encountered unexpected OIDC connector type %T", oc)
|
|
}
|
|
}
|
|
return &types.OIDCConnectorV3List{
|
|
OIDCConnectors: connectors,
|
|
}, nil
|
|
}
|
|
|
|
// CreateOIDCConnector creates a new OIDC connector.
|
|
func (g *GRPCServer) CreateOIDCConnector(ctx context.Context, req *authpb.CreateOIDCConnectorRequest) (*types.OIDCConnectorV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
created, err := auth.ServerWithRoles.CreateOIDCConnector(ctx, req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
v3, ok := created.(*types.OIDCConnectorV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("encountered unexpected OIDC connector type: %T", created)
|
|
}
|
|
|
|
return v3, nil
|
|
}
|
|
|
|
// UpdateOIDCConnector updates an existing OIDC connector.
|
|
func (g *GRPCServer) UpdateOIDCConnector(ctx context.Context, req *authpb.UpdateOIDCConnectorRequest) (*types.OIDCConnectorV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
updated, err := auth.ServerWithRoles.UpdateOIDCConnector(ctx, req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
v3, ok := updated.(*types.OIDCConnectorV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("encountered unexpected OIDC connector type: %T", updated)
|
|
}
|
|
|
|
return v3, nil
|
|
}
|
|
|
|
// UpsertOIDCConnectorV2 creates a new or replaces an existing OIDC connector.
|
|
func (g *GRPCServer) UpsertOIDCConnectorV2(ctx context.Context, req *authpb.UpsertOIDCConnectorRequest) (*types.OIDCConnectorV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
upserted, err := auth.ServerWithRoles.UpsertOIDCConnector(ctx, req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
v3, ok := upserted.(*types.OIDCConnectorV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("encountered unexpected OIDC connector type: %T", upserted)
|
|
}
|
|
|
|
return v3, nil
|
|
}
|
|
|
|
// UpsertOIDCConnector creates a new or replaces an existing OIDC connector.
|
|
// Deprecated: Use [GRPCServer.UpsertOIDCConnectorV2] instead.
|
|
func (g *GRPCServer) UpsertOIDCConnector(ctx context.Context, oidcConnector *types.OIDCConnectorV3) (*emptypb.Empty, error) {
|
|
if _, err := g.UpsertOIDCConnectorV2(ctx, &authpb.UpsertOIDCConnectorRequest{Connector: oidcConnector}); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteOIDCConnector deletes an OIDC connector by name.
|
|
func (g *GRPCServer) DeleteOIDCConnector(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteOIDCConnector(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// CreateOIDCAuthRequest creates OIDCAuthRequest
|
|
func (g *GRPCServer) CreateOIDCAuthRequest(ctx context.Context, req *types.OIDCAuthRequest) (*types.OIDCAuthRequest, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
response, err := auth.CreateOIDCAuthRequest(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// GetOIDCAuthRequest gets OIDC AuthnRequest
|
|
func (g *GRPCServer) GetOIDCAuthRequest(ctx context.Context, req *authpb.GetOIDCAuthRequestRequest) (*types.OIDCAuthRequest, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
request, err := auth.ServerWithRoles.GetOIDCAuthRequest(ctx, req.StateToken)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
// GetSAMLConnector retrieves a SAML connector by name.
|
|
func (g *GRPCServer) GetSAMLConnector(ctx context.Context, req *types.ResourceWithSecretsRequest) (*types.SAMLConnectorV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sc, err := auth.ServerWithRoles.GetSAMLConnector(ctx, req.Name, req.WithSecrets, types.SAMLConnectorValidationFollowURLs(!req.SAMLValidationNoFollowURLs))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
samlConnectorV2, ok := sc.(*types.SAMLConnectorV2)
|
|
if !ok {
|
|
return nil, trace.Errorf("encountered unexpected SAML connector type: %T", sc)
|
|
}
|
|
return samlConnectorV2, nil
|
|
}
|
|
|
|
// GetSAMLConnectors retrieves valid SAML connectors, errors from individual connectors are not forwarded.
|
|
func (g *GRPCServer) GetSAMLConnectors(ctx context.Context, req *types.ResourcesWithSecretsRequest) (*types.SAMLConnectorV2List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
scs, err := auth.ServerWithRoles.GetSAMLConnectors(ctx, req.WithSecrets, types.SAMLConnectorValidationFollowURLs(!req.SAMLValidationNoFollowURLs))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
samlConnectorsV2 := make([]*types.SAMLConnectorV2, len(scs))
|
|
for i, sc := range scs {
|
|
var ok bool
|
|
if samlConnectorsV2[i], ok = sc.(*types.SAMLConnectorV2); !ok {
|
|
return nil, trace.Errorf("encountered unexpected SAML connector type: %T", sc)
|
|
}
|
|
}
|
|
return &types.SAMLConnectorV2List{
|
|
SAMLConnectors: samlConnectorsV2,
|
|
}, nil
|
|
}
|
|
|
|
// CreateSAMLConnector creates a new SAML connector.
|
|
func (g *GRPCServer) CreateSAMLConnector(ctx context.Context, req *authpb.CreateSAMLConnectorRequest) (*types.SAMLConnectorV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
created, err := auth.ServerWithRoles.CreateSAMLConnector(ctx, req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
v2, ok := created.(*types.SAMLConnectorV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("encountered unexpected SAML connector type: %T", created)
|
|
}
|
|
|
|
return v2, nil
|
|
}
|
|
|
|
// UpdateSAMLConnector updates an existing SAML connector.
|
|
func (g *GRPCServer) UpdateSAMLConnector(ctx context.Context, req *authpb.UpdateSAMLConnectorRequest) (*types.SAMLConnectorV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
updated, err := auth.ServerWithRoles.UpdateSAMLConnector(ctx, req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
v2, ok := updated.(*types.SAMLConnectorV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("encountered unexpected SAML connector type: %T", updated)
|
|
}
|
|
|
|
return v2, nil
|
|
}
|
|
|
|
// UpsertSAMLConnectorV2 creates a new or replaces an existing SAML connector.
|
|
func (g *GRPCServer) UpsertSAMLConnectorV2(ctx context.Context, req *authpb.UpsertSAMLConnectorRequest) (*types.SAMLConnectorV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
upserted, err := auth.ServerWithRoles.UpsertSAMLConnector(ctx, req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
v2, ok := upserted.(*types.SAMLConnectorV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("encountered unexpected SAML connector type: %T", upserted)
|
|
}
|
|
|
|
return v2, nil
|
|
}
|
|
|
|
// UpsertSAMLConnector upserts a SAML connector.
|
|
// Deprecated: Use [GRPCServer.UpsertSAMLConnectorV2] instead.
|
|
func (g *GRPCServer) UpsertSAMLConnector(ctx context.Context, samlConnector *types.SAMLConnectorV2) (*emptypb.Empty, error) {
|
|
if _, err := g.UpsertSAMLConnectorV2(ctx, &authpb.UpsertSAMLConnectorRequest{Connector: samlConnector}); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteSAMLConnector deletes a SAML connector by name.
|
|
func (g *GRPCServer) DeleteSAMLConnector(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteSAMLConnector(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// CreateSAMLAuthRequest creates SAMLAuthRequest.
|
|
func (g *GRPCServer) CreateSAMLAuthRequest(ctx context.Context, req *types.SAMLAuthRequest) (*types.SAMLAuthRequest, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
response, err := auth.CreateSAMLAuthRequest(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// GetSAMLAuthRequest gets a SAMLAuthRequest by id.
|
|
func (g *GRPCServer) GetSAMLAuthRequest(ctx context.Context, req *authpb.GetSAMLAuthRequestRequest) (*types.SAMLAuthRequest, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
request, err := auth.GetSAMLAuthRequest(ctx, req.ID)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
// GetGithubConnector retrieves a Github connector by name.
|
|
func (g *GRPCServer) GetGithubConnector(ctx context.Context, req *types.ResourceWithSecretsRequest) (*types.GithubConnectorV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
gc, err := auth.ServerWithRoles.GetGithubConnector(ctx, req.Name, req.WithSecrets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
githubConnectorV3, ok := gc.(*types.GithubConnectorV3)
|
|
if !ok {
|
|
return nil, trace.Errorf("encountered unexpected GitHub connector type: %T", gc)
|
|
}
|
|
return githubConnectorV3, nil
|
|
}
|
|
|
|
// GetGithubConnectors retrieves valid GitHub connectors, errors from individual connectors are not forwarded.
|
|
func (g *GRPCServer) GetGithubConnectors(ctx context.Context, req *types.ResourcesWithSecretsRequest) (*types.GithubConnectorV3List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
gcs, err := auth.ServerWithRoles.GetGithubConnectors(ctx, req.WithSecrets)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
githubConnectorsV3 := make([]*types.GithubConnectorV3, len(gcs))
|
|
for i, gc := range gcs {
|
|
var ok bool
|
|
if githubConnectorsV3[i], ok = gc.(*types.GithubConnectorV3); !ok {
|
|
return nil, trace.Errorf("encountered unexpected GitHub connector type: %T", gc)
|
|
}
|
|
}
|
|
return &types.GithubConnectorV3List{
|
|
GithubConnectors: githubConnectorsV3,
|
|
}, nil
|
|
}
|
|
|
|
// UpsertGithubConnectorV2 creates a new or replaces an existing Github connector.
|
|
func (g *GRPCServer) UpsertGithubConnectorV2(ctx context.Context, req *authpb.UpsertGithubConnectorRequest) (*types.GithubConnectorV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
githubConnector, err := services.InitGithubConnector(req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
upserted, err := auth.ServerWithRoles.UpsertGithubConnector(ctx, githubConnector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
githubConnectorV3, err := services.ConvertGithubConnector(upserted)
|
|
return githubConnectorV3, trace.Wrap(err)
|
|
}
|
|
|
|
// UpsertGithubConnector creates a new or replaces an existing Github connector.
|
|
// Deprecated: Use [GRPCServer.UpsertGithubConnectorV2] instead.
|
|
func (g *GRPCServer) UpsertGithubConnector(ctx context.Context, connector *types.GithubConnectorV3) (*emptypb.Empty, error) {
|
|
if _, err := g.UpsertGithubConnectorV2(ctx, &authpb.UpsertGithubConnectorRequest{Connector: connector}); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpdateGithubConnector updates an existing Github connector.
|
|
func (g *GRPCServer) UpdateGithubConnector(ctx context.Context, req *authpb.UpdateGithubConnectorRequest) (*types.GithubConnectorV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
githubConnector, err := services.InitGithubConnector(req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
updated, err := auth.ServerWithRoles.UpdateGithubConnector(ctx, githubConnector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
githubConnectorV3, err := services.ConvertGithubConnector(updated)
|
|
return githubConnectorV3, trace.Wrap(err)
|
|
}
|
|
|
|
// CreateGithubConnector creates a new Github connector.
|
|
func (g *GRPCServer) CreateGithubConnector(ctx context.Context, req *authpb.CreateGithubConnectorRequest) (*types.GithubConnectorV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
githubConnector, err := services.InitGithubConnector(req.Connector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
created, err := auth.ServerWithRoles.CreateGithubConnector(ctx, githubConnector)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
githubConnectorV3, err := services.ConvertGithubConnector(created)
|
|
return githubConnectorV3, trace.Wrap(err)
|
|
}
|
|
|
|
// DeleteGithubConnector deletes a Github connector by name.
|
|
func (g *GRPCServer) DeleteGithubConnector(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteGithubConnector(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// CreateGithubAuthRequest creates GithubAuthRequest.
|
|
func (g *GRPCServer) CreateGithubAuthRequest(ctx context.Context, req *types.GithubAuthRequest) (*types.GithubAuthRequest, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
response, err := auth.CreateGithubAuthRequest(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
// GetGithubAuthRequest gets a GithubAuthRequest by id.
|
|
func (g *GRPCServer) GetGithubAuthRequest(ctx context.Context, req *authpb.GetGithubAuthRequestRequest) (*types.GithubAuthRequest, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
request, err := auth.GetGithubAuthRequest(ctx, req.StateToken)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
// GetSSODiagnosticInfo gets a SSO diagnostic info for a specific SSO auth request.
|
|
func (g *GRPCServer) GetSSODiagnosticInfo(ctx context.Context, req *authpb.GetSSODiagnosticInfoRequest) (*types.SSODiagnosticInfo, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
info, err := auth.GetSSODiagnosticInfo(ctx, req.AuthRequestKind, req.AuthRequestID)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return info, nil
|
|
}
|
|
|
|
// GetServerInfos returns a stream of ServerInfos.
|
|
func (g *GRPCServer) GetServerInfos(_ *emptypb.Empty, stream authpb.AuthService_GetServerInfosServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
infos := auth.GetServerInfos(stream.Context())
|
|
for infos.Next() {
|
|
si, ok := infos.Item().(*types.ServerInfoV1)
|
|
if !ok {
|
|
g.logger.WarnContext(stream.Context(), "expected type ServerInfoV1, got unexpected type",
|
|
"server_info_type", logutils.TypeAttr(infos.Item()),
|
|
"server_info_name", infos.Item().GetName(),
|
|
)
|
|
}
|
|
if err := stream.Send(si); err != nil {
|
|
infos.Done()
|
|
if errors.Is(err, io.EOF) {
|
|
return nil
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
return trace.Wrap(infos.Done())
|
|
}
|
|
|
|
// GetServerInfo returns a ServerInfo by name.
|
|
func (g *GRPCServer) GetServerInfo(ctx context.Context, req *types.ResourceRequest) (*types.ServerInfoV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
si, err := auth.ServerWithRoles.GetServerInfo(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
serverInfoV1, ok := si.(*types.ServerInfoV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("encountered unexpected Server Info type %T", si)
|
|
}
|
|
return serverInfoV1, nil
|
|
}
|
|
|
|
// UpsertServerInfo upserts a ServerInfo.
|
|
func (g *GRPCServer) UpsertServerInfo(ctx context.Context, si *types.ServerInfoV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.UpsertServerInfo(ctx, si); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteServerInfo deletes a ServerInfo by name.
|
|
func (g *GRPCServer) DeleteServerInfo(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteServerInfo(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllServerInfos deletes all ServerInfos.
|
|
func (g *GRPCServer) DeleteAllServerInfos(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteAllServerInfos(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetTrustedCluster retrieves a Trusted Cluster by name.
|
|
func (g *GRPCServer) GetTrustedCluster(ctx context.Context, req *types.ResourceRequest) (*types.TrustedClusterV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tc, err := auth.ServerWithRoles.GetTrustedCluster(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
trustedClusterV2, ok := tc.(*types.TrustedClusterV2)
|
|
if !ok {
|
|
return nil, trace.Errorf("encountered unexpected Trusted Cluster type %T", tc)
|
|
}
|
|
return trustedClusterV2, nil
|
|
}
|
|
|
|
// GetTrustedClusters retrieves all Trusted Clusters.
|
|
func (g *GRPCServer) GetTrustedClusters(ctx context.Context, _ *emptypb.Empty) (*types.TrustedClusterV2List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tcs, err := auth.ServerWithRoles.GetTrustedClusters(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
trustedClustersV2 := make([]*types.TrustedClusterV2, len(tcs))
|
|
for i, tc := range tcs {
|
|
var ok bool
|
|
if trustedClustersV2[i], ok = tc.(*types.TrustedClusterV2); !ok {
|
|
return nil, trace.Errorf("encountered unexpected Trusted Cluster type: %T", tc)
|
|
}
|
|
}
|
|
return &types.TrustedClusterV2List{
|
|
TrustedClusters: trustedClustersV2,
|
|
}, nil
|
|
}
|
|
|
|
// UpsertTrustedCluster upserts a Trusted Cluster.
|
|
//
|
|
// Deprecated: Use UpsertTrustedClusterV2 instead.
|
|
func (g *GRPCServer) UpsertTrustedCluster(ctx context.Context, cluster *types.TrustedClusterV2) (*types.TrustedClusterV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err = services.ValidateTrustedCluster(cluster); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
tc, err := auth.ServerWithRoles.UpsertTrustedCluster(ctx, cluster)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
trustedClusterV2, ok := tc.(*types.TrustedClusterV2)
|
|
if !ok {
|
|
return nil, trace.Errorf("encountered unexpected Trusted Cluster type: %T", tc)
|
|
}
|
|
return trustedClusterV2, nil
|
|
}
|
|
|
|
// DeleteTrustedCluster deletes a Trusted Cluster by name.
|
|
func (g *GRPCServer) DeleteTrustedCluster(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteTrustedCluster(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetToken retrieves a token by name.
|
|
func (g *GRPCServer) GetToken(ctx context.Context, req *types.ResourceRequest) (*types.ProvisionTokenV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
t, err := auth.ServerWithRoles.GetToken(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
provisionTokenV2, ok := t.(*types.ProvisionTokenV2)
|
|
if !ok {
|
|
return nil, trace.Errorf("encountered unexpected token type: %T", t)
|
|
}
|
|
return provisionTokenV2, nil
|
|
}
|
|
|
|
// GetTokens retrieves all tokens.
|
|
func (g *GRPCServer) GetTokens(ctx context.Context, _ *emptypb.Empty) (*types.ProvisionTokenV2List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
ts, err := auth.ServerWithRoles.GetTokens(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
provisionTokensV2 := make([]*types.ProvisionTokenV2, len(ts))
|
|
for i, t := range ts {
|
|
var ok bool
|
|
if provisionTokensV2[i], ok = t.(*types.ProvisionTokenV2); !ok {
|
|
return nil, trace.Errorf("encountered unexpected token type: %T", t)
|
|
}
|
|
}
|
|
return &types.ProvisionTokenV2List{
|
|
ProvisionTokens: provisionTokensV2,
|
|
}, nil
|
|
}
|
|
|
|
// UpsertTokenV2 upserts a token.
|
|
func (g *GRPCServer) UpsertTokenV2(ctx context.Context, req *authpb.UpsertTokenV2Request) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// When new token versions are introduced, this can be exchanged for a
|
|
// switch statement.
|
|
token := req.GetV2()
|
|
if token == nil {
|
|
return nil, trail.ToGRPC(
|
|
trace.BadParameter("token not provided in request"),
|
|
)
|
|
}
|
|
if err = auth.ServerWithRoles.UpsertToken(ctx, token); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// CreateTokenV2 creates a token.
|
|
func (g *GRPCServer) CreateTokenV2(ctx context.Context, req *authpb.CreateTokenV2Request) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// When new token versions are introduced, this can be exchanged for a
|
|
// switch statement.
|
|
token := req.GetV2()
|
|
if token == nil {
|
|
return nil, trail.ToGRPC(
|
|
trace.BadParameter("token not provided in request"),
|
|
)
|
|
}
|
|
if err = auth.ServerWithRoles.CreateToken(ctx, token); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteToken deletes a token by name.
|
|
func (g *GRPCServer) DeleteToken(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ServerWithRoles.DeleteToken(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetNode retrieves a node by name and namespace.
|
|
func (g *GRPCServer) GetNode(ctx context.Context, req *types.ResourceInNamespaceRequest) (*types.ServerV2, error) {
|
|
if req.Namespace == "" {
|
|
return nil, trace.BadParameter("missing parameter namespace")
|
|
}
|
|
if req.Name == "" {
|
|
return nil, trace.BadParameter("missing parameter name")
|
|
}
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
node, err := auth.ServerWithRoles.GetNode(ctx, req.Namespace, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
serverV2, ok := node.(*types.ServerV2)
|
|
if !ok {
|
|
return nil, trace.Errorf("encountered unexpected node type: %T", node)
|
|
}
|
|
return serverV2, nil
|
|
}
|
|
|
|
// UpsertNode upserts a node.
|
|
func (g *GRPCServer) UpsertNode(ctx context.Context, node *types.ServerV2) (*types.KeepAlive, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Extract peer (remote host) from context and if the node sent 0.0.0.0 as
|
|
// its address (meaning it did not set an advertise address) update it with
|
|
// the address of the peer.
|
|
p, ok := peer.FromContext(ctx)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unable to find peer")
|
|
}
|
|
node.SetAddr(utils.ReplaceLocalhost(node.GetAddr(), p.Addr.String()))
|
|
|
|
keepAlive, err := auth.ServerWithRoles.UpsertNode(ctx, node)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return keepAlive, nil
|
|
}
|
|
|
|
// DeleteNode deletes a node by name.
|
|
func (g *GRPCServer) DeleteNode(ctx context.Context, req *types.ResourceInNamespaceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err = auth.ServerWithRoles.DeleteNode(ctx, req.Namespace, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllNodes deletes all nodes in a given namespace.
|
|
func (g *GRPCServer) DeleteAllNodes(ctx context.Context, req *types.ResourcesInNamespaceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err = auth.ServerWithRoles.DeleteAllNodes(ctx, req.Namespace); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetClusterAuditConfig gets cluster audit configuration.
|
|
func (g *GRPCServer) GetClusterAuditConfig(ctx context.Context, _ *emptypb.Empty) (*types.ClusterAuditConfigV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
auditConfig, err := auth.ServerWithRoles.GetClusterAuditConfig(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
auditConfigV2, ok := auditConfig.(*types.ClusterAuditConfigV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", auditConfig)
|
|
}
|
|
return auditConfigV2, nil
|
|
}
|
|
|
|
// GetClusterNetworkingConfig gets cluster networking configuration.
|
|
func (g *GRPCServer) GetClusterNetworkingConfig(ctx context.Context, _ *emptypb.Empty) (*types.ClusterNetworkingConfigV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
netConfig, err := auth.ServerWithRoles.GetClusterNetworkingConfig(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
netConfigV2, ok := netConfig.(*types.ClusterNetworkingConfigV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", netConfig)
|
|
}
|
|
return netConfigV2, nil
|
|
}
|
|
|
|
// SetClusterNetworkingConfig sets cluster networking configuration.
|
|
func (g *GRPCServer) SetClusterNetworkingConfig(ctx context.Context, netConfig *types.ClusterNetworkingConfigV2) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
netConfig.SetOrigin(types.OriginDynamic)
|
|
if err = auth.ServerWithRoles.SetClusterNetworkingConfig(ctx, netConfig); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// ResetClusterNetworkingConfig resets cluster networking configuration to defaults.
|
|
func (g *GRPCServer) ResetClusterNetworkingConfig(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err = auth.ServerWithRoles.ResetClusterNetworkingConfig(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetSessionRecordingConfig gets session recording configuration.
|
|
func (g *GRPCServer) GetSessionRecordingConfig(ctx context.Context, _ *emptypb.Empty) (*types.SessionRecordingConfigV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
recConfig, err := auth.ServerWithRoles.GetSessionRecordingConfig(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
recConfigV2, ok := recConfig.(*types.SessionRecordingConfigV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", recConfig)
|
|
}
|
|
return recConfigV2, nil
|
|
}
|
|
|
|
// SetSessionRecordingConfig sets session recording configuration.
|
|
func (g *GRPCServer) SetSessionRecordingConfig(ctx context.Context, recConfig *types.SessionRecordingConfigV2) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
recConfig.SetOrigin(types.OriginDynamic)
|
|
if err = auth.ServerWithRoles.SetSessionRecordingConfig(ctx, recConfig); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// ResetSessionRecordingConfig resets session recording configuration to defaults.
|
|
func (g *GRPCServer) ResetSessionRecordingConfig(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err = auth.ServerWithRoles.ResetSessionRecordingConfig(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetAuthPreference gets cluster auth preference.
|
|
func (g *GRPCServer) GetAuthPreference(ctx context.Context, _ *emptypb.Empty) (*types.AuthPreferenceV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
authPref, err := auth.ServerWithRoles.GetAuthPreference(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
authPrefV2, ok := authPref.(*types.AuthPreferenceV2)
|
|
if !ok {
|
|
return nil, trace.Wrap(trace.BadParameter("unexpected type %T", authPref))
|
|
}
|
|
return authPrefV2, nil
|
|
}
|
|
|
|
// SetAuthPreference sets cluster auth preference.
|
|
// Deprecated: Use Update/UpsertAuthPreference where appropriate.
|
|
func (g *GRPCServer) SetAuthPreference(ctx context.Context, authPref *types.AuthPreferenceV2) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
authPref.SetOrigin(types.OriginDynamic)
|
|
if err = auth.ServerWithRoles.SetAuthPreference(ctx, authPref); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// ResetAuthPreference resets cluster auth preference to defaults.
|
|
func (g *GRPCServer) ResetAuthPreference(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err = auth.ServerWithRoles.ResetAuthPreference(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// StreamSessionEvents streams all events from a given session recording. An error is returned on the first
|
|
// channel if one is encountered. Otherwise the event channel is closed when the stream ends.
|
|
// The event channel is not closed on error to prevent race conditions in downstream select statements.
|
|
func (g *GRPCServer) StreamSessionEvents(req *authpb.StreamSessionEventsRequest, stream authpb.AuthService_StreamSessionEventsServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
c, e := auth.ServerWithRoles.StreamSessionEvents(stream.Context(), session.ID(req.SessionID), int64(req.StartIndex))
|
|
for {
|
|
select {
|
|
case event, more := <-c:
|
|
if !more {
|
|
return nil
|
|
}
|
|
|
|
oneOf, err := apievents.ToOneOf(event)
|
|
if err != nil {
|
|
return trail.ToGRPC(trace.Wrap(err))
|
|
}
|
|
|
|
if err := stream.Send(oneOf); err != nil {
|
|
return trail.ToGRPC(trace.Wrap(err))
|
|
}
|
|
case err := <-e:
|
|
return trail.ToGRPC(trace.Wrap(err))
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetNetworkRestrictions retrieves all the network restrictions (allow/deny lists).
|
|
func (g *GRPCServer) GetNetworkRestrictions(ctx context.Context, _ *emptypb.Empty) (*types.NetworkRestrictionsV4, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
nr, err := auth.ServerWithRoles.GetNetworkRestrictions(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
restrictionsV4, ok := nr.(*types.NetworkRestrictionsV4)
|
|
if !ok {
|
|
return nil, trace.Wrap(trace.BadParameter("unexpected type %T", nr))
|
|
}
|
|
return restrictionsV4, nil
|
|
}
|
|
|
|
// SetNetworkRestrictions updates the network restrictions.
|
|
func (g *GRPCServer) SetNetworkRestrictions(ctx context.Context, nr *types.NetworkRestrictionsV4) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
if err = auth.ServerWithRoles.SetNetworkRestrictions(ctx, nr); err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteNetworkRestrictions deletes the network restrictions.
|
|
func (g *GRPCServer) DeleteNetworkRestrictions(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
|
|
if err = auth.ServerWithRoles.DeleteNetworkRestrictions(ctx); err != nil {
|
|
return nil, trail.ToGRPC(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetEvents searches for events on the backend and sends them back in a response.
|
|
func (g *GRPCServer) GetEvents(ctx context.Context, req *authpb.GetEventsRequest) (*authpb.Events, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rawEvents, lastkey, err := auth.ServerWithRoles.SearchEvents(ctx, events.SearchEventsRequest{
|
|
From: req.StartDate,
|
|
To: req.EndDate,
|
|
EventTypes: req.EventTypes,
|
|
Limit: int(req.Limit),
|
|
Order: types.EventOrder(req.Order),
|
|
StartKey: req.StartKey,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
var res *authpb.Events = &authpb.Events{}
|
|
|
|
encodedEvents := make([]*apievents.OneOf, 0, len(rawEvents))
|
|
|
|
for _, rawEvent := range rawEvents {
|
|
event, err := apievents.ToOneOf(rawEvent)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
encodedEvents = append(encodedEvents, event)
|
|
}
|
|
|
|
res.Items = encodedEvents
|
|
res.LastKey = lastkey
|
|
return res, nil
|
|
}
|
|
|
|
// GetSessionEvents searches for session events on the backend and sends them back in a response.
|
|
func (g *GRPCServer) GetSessionEvents(ctx context.Context, req *authpb.GetSessionEventsRequest) (*authpb.Events, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rawEvents, lastkey, err := auth.ServerWithRoles.SearchSessionEvents(ctx, events.SearchSessionEventsRequest{
|
|
From: req.StartDate,
|
|
To: req.EndDate,
|
|
Limit: int(req.Limit),
|
|
Order: types.EventOrder(req.Order),
|
|
StartKey: req.StartKey,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
var res *authpb.Events = &authpb.Events{}
|
|
|
|
encodedEvents := make([]*apievents.OneOf, 0, len(rawEvents))
|
|
|
|
for _, rawEvent := range rawEvents {
|
|
event, err := apievents.ToOneOf(rawEvent)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
encodedEvents = append(encodedEvents, event)
|
|
}
|
|
|
|
res.Items = encodedEvents
|
|
res.LastKey = lastkey
|
|
return res, nil
|
|
}
|
|
|
|
// GetLock retrieves a lock by name.
|
|
func (g *GRPCServer) GetLock(ctx context.Context, req *authpb.GetLockRequest) (*types.LockV2, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
lock, err := auth.GetLock(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
lockV2, ok := lock.(*types.LockV2)
|
|
if !ok {
|
|
return nil, trace.Errorf("unexpected lock type %T", lock)
|
|
}
|
|
return lockV2, nil
|
|
}
|
|
|
|
// GetLocks gets all/in-force locks that match at least one of the targets when specified.
|
|
func (g *GRPCServer) GetLocks(ctx context.Context, req *authpb.GetLocksRequest) (*authpb.GetLocksResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
targets := make([]types.LockTarget, 0, len(req.Targets))
|
|
for _, targetPtr := range req.Targets {
|
|
if targetPtr != nil {
|
|
targets = append(targets, *targetPtr)
|
|
}
|
|
}
|
|
locks, err := auth.GetLocks(ctx, req.InForceOnly, targets...)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
lockV2s := make([]*types.LockV2, 0, len(locks))
|
|
for _, lock := range locks {
|
|
lockV2, ok := lock.(*types.LockV2)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected lock type %T", lock)
|
|
}
|
|
lockV2s = append(lockV2s, lockV2)
|
|
}
|
|
return &authpb.GetLocksResponse{
|
|
Locks: lockV2s,
|
|
}, nil
|
|
}
|
|
|
|
// UpsertLock upserts a lock.
|
|
func (g *GRPCServer) UpsertLock(ctx context.Context, lock *types.LockV2) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.UpsertLock(ctx, lock); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteLock deletes a lock.
|
|
func (g *GRPCServer) DeleteLock(ctx context.Context, req *authpb.DeleteLockRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteLock(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// ReplaceRemoteLocks replaces the set of locks associated with a remote cluster.
|
|
func (g *GRPCServer) ReplaceRemoteLocks(ctx context.Context, req *authpb.ReplaceRemoteLocksRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
locks := make([]types.Lock, 0, len(req.Locks))
|
|
for _, lock := range req.Locks {
|
|
locks = append(locks, lock)
|
|
}
|
|
if err := auth.ReplaceRemoteLocks(ctx, req.ClusterName, locks); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// CreateApp creates a new application resource.
|
|
func (g *GRPCServer) CreateApp(ctx context.Context, app *types.AppV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if app.Origin() == "" {
|
|
app.SetOrigin(types.OriginDynamic)
|
|
}
|
|
if err := auth.CreateApp(ctx, app); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpdateApp updates existing application resource.
|
|
func (g *GRPCServer) UpdateApp(ctx context.Context, app *types.AppV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if app.Origin() == "" {
|
|
app.SetOrigin(types.OriginDynamic)
|
|
}
|
|
if err := auth.UpdateApp(ctx, app); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetApp returns the specified application resource.
|
|
func (g *GRPCServer) GetApp(ctx context.Context, req *types.ResourceRequest) (*types.AppV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
app, err := auth.GetApp(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
appV3, ok := app.(*types.AppV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unsupported application type %T", app)
|
|
}
|
|
return appV3, nil
|
|
}
|
|
|
|
// GetApps returns all application resources.
|
|
func (g *GRPCServer) GetApps(ctx context.Context, _ *emptypb.Empty) (*types.AppV3List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
apps, err := auth.GetApps(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var appsV3 []*types.AppV3
|
|
for _, app := range apps {
|
|
appV3, ok := app.(*types.AppV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unsupported application type %T", app)
|
|
}
|
|
appsV3 = append(appsV3, appV3)
|
|
}
|
|
return &types.AppV3List{
|
|
Apps: appsV3,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteApp removes the specified application resource.
|
|
func (g *GRPCServer) DeleteApp(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteApp(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllApps removes all application resources.
|
|
func (g *GRPCServer) DeleteAllApps(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteAllApps(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// CreateDatabase creates a new database resource.
|
|
func (g *GRPCServer) CreateDatabase(ctx context.Context, database *types.DatabaseV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if database.Origin() == "" {
|
|
database.SetOrigin(types.OriginDynamic)
|
|
}
|
|
if err := services.ValidateDatabase(database); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.CreateDatabase(ctx, database); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpdateDatabase updates existing database resource.
|
|
func (g *GRPCServer) UpdateDatabase(ctx context.Context, database *types.DatabaseV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if database.Origin() == "" {
|
|
database.SetOrigin(types.OriginDynamic)
|
|
}
|
|
if err := services.ValidateDatabase(database); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.UpdateDatabase(ctx, database); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetDatabase returns the specified database resource.
|
|
func (g *GRPCServer) GetDatabase(ctx context.Context, req *types.ResourceRequest) (*types.DatabaseV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
database, err := auth.GetDatabase(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
databaseV3, ok := database.(*types.DatabaseV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unsupported database type %T", database)
|
|
}
|
|
return databaseV3, nil
|
|
}
|
|
|
|
// GetDatabases returns all database resources.
|
|
func (g *GRPCServer) GetDatabases(ctx context.Context, _ *emptypb.Empty) (*types.DatabaseV3List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
databases, err := auth.GetDatabases(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var databasesV3 []*types.DatabaseV3
|
|
for _, database := range databases {
|
|
databaseV3, ok := database.(*types.DatabaseV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unsupported database type %T", database)
|
|
}
|
|
databasesV3 = append(databasesV3, databaseV3)
|
|
}
|
|
return &types.DatabaseV3List{
|
|
Databases: databasesV3,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteDatabase removes the specified database.
|
|
func (g *GRPCServer) DeleteDatabase(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteDatabase(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllDatabases removes all databases.
|
|
func (g *GRPCServer) DeleteAllDatabases(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteAllDatabases(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetWindowsDesktopServices returns all registered Windows desktop services.
|
|
func (g *GRPCServer) GetWindowsDesktopServices(ctx context.Context, req *emptypb.Empty) (*authpb.GetWindowsDesktopServicesResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
windowsDesktopServices, err := auth.GetWindowsDesktopServices(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var services []*types.WindowsDesktopServiceV3
|
|
for _, s := range windowsDesktopServices {
|
|
service, ok := s.(*types.WindowsDesktopServiceV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", s)
|
|
}
|
|
services = append(services, service)
|
|
}
|
|
return &authpb.GetWindowsDesktopServicesResponse{
|
|
Services: services,
|
|
}, nil
|
|
}
|
|
|
|
// GetWindowsDesktopService returns a registered Windows desktop service by name.
|
|
func (g *GRPCServer) GetWindowsDesktopService(ctx context.Context, req *authpb.GetWindowsDesktopServiceRequest) (*authpb.GetWindowsDesktopServiceResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
windowsDesktopService, err := auth.GetWindowsDesktopService(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
service, ok := windowsDesktopService.(*types.WindowsDesktopServiceV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", service)
|
|
}
|
|
return &authpb.GetWindowsDesktopServiceResponse{
|
|
Service: service,
|
|
}, nil
|
|
}
|
|
|
|
// UpsertWindowsDesktopService registers a new Windows desktop service.
|
|
func (g *GRPCServer) UpsertWindowsDesktopService(ctx context.Context, service *types.WindowsDesktopServiceV3) (*types.KeepAlive, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// 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, err := authz.ClientSrcAddrFromContext(ctx)
|
|
if err != nil {
|
|
g.logger.WarnContext(ctx, "error getting client address from context", "error", err)
|
|
return nil, status.Errorf(codes.FailedPrecondition, "client address not found in request context")
|
|
}
|
|
service.Spec.Addr = utils.ReplaceLocalhost(service.GetAddr(), clientAddr.String())
|
|
|
|
keepAlive, err := auth.UpsertWindowsDesktopService(ctx, service)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return keepAlive, nil
|
|
}
|
|
|
|
// DeleteWindowsDesktopService removes the specified Windows desktop service.
|
|
func (g *GRPCServer) DeleteWindowsDesktopService(ctx context.Context, req *authpb.DeleteWindowsDesktopServiceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteWindowsDesktopService(ctx, req.GetName())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllWindowsDesktopServices removes all registered Windows desktop services.
|
|
func (g *GRPCServer) DeleteAllWindowsDesktopServices(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteAllWindowsDesktopServices(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetWindowsDesktops returns all registered Windows desktop hosts.
|
|
func (g *GRPCServer) GetWindowsDesktops(ctx context.Context, filter *types.WindowsDesktopFilter) (*authpb.GetWindowsDesktopsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
windowsDesktops, err := auth.GetWindowsDesktops(ctx, *filter)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var desktops []*types.WindowsDesktopV3
|
|
for _, s := range windowsDesktops {
|
|
desktop, ok := s.(*types.WindowsDesktopV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", s)
|
|
}
|
|
desktops = append(desktops, desktop)
|
|
}
|
|
return &authpb.GetWindowsDesktopsResponse{
|
|
Desktops: desktops,
|
|
}, nil
|
|
}
|
|
|
|
// CreateWindowsDesktop registers a new Windows desktop host.
|
|
func (g *GRPCServer) CreateWindowsDesktop(ctx context.Context, desktop *types.WindowsDesktopV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.CreateWindowsDesktop(ctx, desktop); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpdateWindowsDesktop updates an existing Windows desktop host.
|
|
func (g *GRPCServer) UpdateWindowsDesktop(ctx context.Context, desktop *types.WindowsDesktopV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.UpdateWindowsDesktop(ctx, desktop); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpsertWindowsDesktop updates a Windows desktop host, creating it if it doesn't exist.
|
|
func (g *GRPCServer) UpsertWindowsDesktop(ctx context.Context, desktop *types.WindowsDesktopV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.UpsertWindowsDesktop(ctx, desktop); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteWindowsDesktop removes the specified windows desktop host.
|
|
// Note: unlike GetWindowsDesktops, this will delete at-most one desktop.
|
|
// Passing an empty host ID will not trigger "delete all" behavior. To delete
|
|
// all desktops, use DeleteAllWindowsDesktops.
|
|
func (g *GRPCServer) DeleteWindowsDesktop(ctx context.Context, req *authpb.DeleteWindowsDesktopRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteWindowsDesktop(ctx, req.GetHostID(), req.GetName())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllWindowsDesktops removes all registered Windows desktop hosts.
|
|
func (g *GRPCServer) DeleteAllWindowsDesktops(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.DeleteAllWindowsDesktops(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GenerateWindowsDesktopCert generates client certificate for Windows RDP
|
|
// authentication.
|
|
func (g *GRPCServer) GenerateWindowsDesktopCert(ctx context.Context, req *authpb.WindowsDesktopCertRequest) (*authpb.WindowsDesktopCertResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
response, err := auth.GenerateWindowsDesktopCert(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return response, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetDesktopBootstrapScript(ctx context.Context, _ *emptypb.Empty) (*authpb.DesktopBootstrapScriptResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
res, err := auth.GetDesktopBootstrapScript(ctx)
|
|
return res, trace.Wrap(err)
|
|
}
|
|
|
|
// ChangeUserAuthentication implements AuthService.ChangeUserAuthentication.
|
|
func (g *GRPCServer) ChangeUserAuthentication(ctx context.Context, req *authpb.ChangeUserAuthenticationRequest) (*authpb.ChangeUserAuthenticationResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
res, err := auth.ServerWithRoles.ChangeUserAuthentication(ctx, req)
|
|
return res, trace.Wrap(err)
|
|
}
|
|
|
|
// StartAccountRecovery is implemented by AuthService.StartAccountRecovery.
|
|
func (g *GRPCServer) StartAccountRecovery(ctx context.Context, req *authpb.StartAccountRecoveryRequest) (*types.UserTokenV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resetToken, err := auth.ServerWithRoles.StartAccountRecovery(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
r, ok := resetToken.(*types.UserTokenV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected UserToken type %T", resetToken)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// VerifyAccountRecovery is implemented by AuthService.VerifyAccountRecovery.
|
|
func (g *GRPCServer) VerifyAccountRecovery(ctx context.Context, req *authpb.VerifyAccountRecoveryRequest) (*types.UserTokenV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
approvedToken, err := auth.ServerWithRoles.VerifyAccountRecovery(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
r, ok := approvedToken.(*types.UserTokenV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected UserToken type %T", approvedToken)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// CompleteAccountRecovery is implemented by AuthService.CompleteAccountRecovery.
|
|
func (g *GRPCServer) CompleteAccountRecovery(ctx context.Context, req *authpb.CompleteAccountRecoveryRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
err = auth.ServerWithRoles.CompleteAccountRecovery(ctx, req)
|
|
return &emptypb.Empty{}, trace.Wrap(err)
|
|
}
|
|
|
|
// CreateAccountRecoveryCodes is implemented by AuthService.CreateAccountRecoveryCodes.
|
|
func (g *GRPCServer) CreateAccountRecoveryCodes(ctx context.Context, req *authpb.CreateAccountRecoveryCodesRequest) (*authpb.RecoveryCodes, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
res, err := auth.ServerWithRoles.CreateAccountRecoveryCodes(ctx, req)
|
|
return res, trace.Wrap(err)
|
|
}
|
|
|
|
// GetAccountRecoveryToken is implemented by AuthService.GetAccountRecoveryToken.
|
|
func (g *GRPCServer) GetAccountRecoveryToken(ctx context.Context, req *authpb.GetAccountRecoveryTokenRequest) (*types.UserTokenV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
approvedToken, err := auth.ServerWithRoles.GetAccountRecoveryToken(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
r, ok := approvedToken.(*types.UserTokenV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected UserToken type %T", approvedToken)
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// GetAccountRecoveryCodes is implemented by AuthService.GetAccountRecoveryCodes.
|
|
func (g *GRPCServer) GetAccountRecoveryCodes(ctx context.Context, req *authpb.GetAccountRecoveryCodesRequest) (*authpb.RecoveryCodes, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rc, err := auth.ServerWithRoles.GetAccountRecoveryCodes(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return rc, nil
|
|
}
|
|
|
|
// CreateAuthenticateChallenge is implemented by AuthService.CreateAuthenticateChallenge.
|
|
func (g *GRPCServer) CreateAuthenticateChallenge(ctx context.Context, req *authpb.CreateAuthenticateChallengeRequest) (*authpb.MFAAuthenticateChallenge, error) {
|
|
actx, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
res, err := actx.ServerWithRoles.CreateAuthenticateChallenge(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// CreatePrivilegeToken is implemented by AuthService.CreatePrivilegeToken.
|
|
func (g *GRPCServer) CreatePrivilegeToken(ctx context.Context, req *authpb.CreatePrivilegeTokenRequest) (*types.UserTokenV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
token, err := auth.CreatePrivilegeToken(ctx, req)
|
|
return token, trace.Wrap(err)
|
|
}
|
|
|
|
// CreateRegisterChallenge is implemented by AuthService.CreateRegisterChallenge.
|
|
func (g *GRPCServer) CreateRegisterChallenge(ctx context.Context, req *authpb.CreateRegisterChallengeRequest) (*authpb.MFARegisterChallenge, error) {
|
|
actx, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
res, err := actx.ServerWithRoles.CreateRegisterChallenge(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// GenerateCertAuthorityCRL returns a CRL for a CA.
|
|
func (g *GRPCServer) GenerateCertAuthorityCRL(ctx context.Context, req *authpb.CertAuthorityRequest) (*authpb.CRL, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
crl, err := auth.GenerateCertAuthorityCRL(ctx, req.Type)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &authpb.CRL{CRL: crl}, nil
|
|
}
|
|
|
|
// ListUnifiedResources retrieves a paginated list of unified resources.
|
|
func (g *GRPCServer) ListUnifiedResources(ctx context.Context, req *authpb.ListUnifiedResourcesRequest) (*authpb.ListUnifiedResourcesResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return auth.ListUnifiedResources(ctx, req)
|
|
}
|
|
|
|
// ListResources retrieves a paginated list of resources.
|
|
func (g *GRPCServer) ListResources(ctx context.Context, req *authpb.ListResourcesRequest) (*authpb.ListResourcesResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
resp, err := auth.ListResources(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
paginatedResources, err := services.MakePaginatedResources(ctx, req.ResourceType, resp.Resources, nil /* requestable map */)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "making paginated resources")
|
|
}
|
|
protoResp := &authpb.ListResourcesResponse{
|
|
NextKey: resp.NextKey,
|
|
Resources: paginatedResources,
|
|
TotalCount: int32(resp.TotalCount),
|
|
}
|
|
|
|
return protoResp, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetSSHTargets(ctx context.Context, req *authpb.GetSSHTargetsRequest) (*authpb.GetSSHTargetsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rsp, err := auth.ServerWithRoles.GetSSHTargets(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return rsp, nil
|
|
}
|
|
|
|
// ResolveSSHTarget gets a server that would match an equivalent ssh dial request.
|
|
func (g *GRPCServer) ResolveSSHTarget(ctx context.Context, req *authpb.ResolveSSHTargetRequest) (*authpb.ResolveSSHTargetResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rsp, err := auth.ServerWithRoles.ResolveSSHTarget(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return rsp, nil
|
|
}
|
|
|
|
// CreateSessionTracker creates a tracker resource for an active session.
|
|
func (g *GRPCServer) CreateSessionTracker(ctx context.Context, req *authpb.CreateSessionTrackerRequest) (*types.SessionTrackerV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if req.SessionTracker == nil {
|
|
g.logger.ErrorContext(ctx, "Missing SessionTracker in CreateSessionTrackerRequest, this can be caused by an outdated Teleport node running against your cluster")
|
|
return nil, trace.BadParameter("missing SessionTracker from CreateSessionTrackerRequest")
|
|
}
|
|
|
|
tracker, err := auth.ServerWithRoles.CreateSessionTracker(ctx, req.SessionTracker)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
v1, ok := tracker.(*types.SessionTrackerV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected session type %T", tracker)
|
|
}
|
|
|
|
return v1, nil
|
|
}
|
|
|
|
// GetSessionTracker returns the current state of a session tracker for an active session.
|
|
func (g *GRPCServer) GetSessionTracker(ctx context.Context, req *authpb.GetSessionTrackerRequest) (*types.SessionTrackerV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
session, err := auth.ServerWithRoles.GetSessionTracker(ctx, req.SessionID)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
defined, ok := session.(*types.SessionTrackerV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected session type %T", session)
|
|
}
|
|
|
|
return defined, nil
|
|
}
|
|
|
|
// GetActiveSessionTrackers returns a list of active session trackers.
|
|
func (g *GRPCServer) GetActiveSessionTrackers(_ *emptypb.Empty, stream authpb.AuthService_GetActiveSessionTrackersServer) error {
|
|
ctx := stream.Context()
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
sessions, err := auth.ServerWithRoles.GetActiveSessionTrackers(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
for _, session := range sessions {
|
|
defined, ok := session.(*types.SessionTrackerV1)
|
|
if !ok {
|
|
return trace.BadParameter("unexpected session type %T", session)
|
|
}
|
|
|
|
err := stream.Send(defined)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetActiveSessionTrackersWithFilter returns a list of active sessions filtered by a filter.
|
|
func (g *GRPCServer) GetActiveSessionTrackersWithFilter(filter *types.SessionTrackerFilter, stream authpb.AuthService_GetActiveSessionTrackersWithFilterServer) error {
|
|
ctx := stream.Context()
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
sessions, err := auth.ServerWithRoles.GetActiveSessionTrackersWithFilter(ctx, filter)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
for _, session := range sessions {
|
|
defined, ok := session.(*types.SessionTrackerV1)
|
|
if !ok {
|
|
return trace.BadParameter("unexpected session type %T", session)
|
|
}
|
|
|
|
err := stream.Send(defined)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveSessionTracker removes a tracker resource for an active session.
|
|
func (g *GRPCServer) RemoveSessionTracker(ctx context.Context, req *authpb.RemoveSessionTrackerRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.ServerWithRoles.RemoveSessionTracker(ctx, req.SessionID)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpdateSessionTracker updates a tracker resource for an active session.
|
|
func (g *GRPCServer) UpdateSessionTracker(ctx context.Context, req *authpb.UpdateSessionTrackerRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
err = auth.ServerWithRoles.UpdateSessionTracker(ctx, req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetDomainName returns local auth domain of the current auth server.
|
|
func (g *GRPCServer) GetDomainName(ctx context.Context, req *emptypb.Empty) (*authpb.GetDomainNameResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
dn, err := auth.ServerWithRoles.GetDomainName(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &authpb.GetDomainNameResponse{
|
|
DomainName: dn,
|
|
}, nil
|
|
}
|
|
|
|
// GetClusterCACert returns the PEM-encoded TLS certs for the local cluster
|
|
// without signing keys. If the cluster has multiple TLS certs, they will all
|
|
// be appended.
|
|
func (g *GRPCServer) GetClusterCACert(
|
|
ctx context.Context, req *emptypb.Empty,
|
|
) (*authpb.GetClusterCACertResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return auth.ServerWithRoles.GetClusterCACert(ctx)
|
|
}
|
|
|
|
// GetConnectionDiagnostic reads a connection diagnostic.
|
|
func (g *GRPCServer) GetConnectionDiagnostic(ctx context.Context, req *authpb.GetConnectionDiagnosticRequest) (*types.ConnectionDiagnosticV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
connectionDiagnostic, err := auth.ServerWithRoles.GetConnectionDiagnostic(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
connectionDiagnosticV1, ok := connectionDiagnostic.(*types.ConnectionDiagnosticV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected connection diagnostic type %T", connectionDiagnostic)
|
|
}
|
|
|
|
return connectionDiagnosticV1, nil
|
|
}
|
|
|
|
// CreateConnectionDiagnostic creates a connection diagnostic
|
|
func (g *GRPCServer) CreateConnectionDiagnostic(ctx context.Context, connectionDiagnostic *types.ConnectionDiagnosticV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.ServerWithRoles.CreateConnectionDiagnostic(ctx, connectionDiagnostic); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// SetInstaller sets the installer script resource
|
|
func (g *GRPCServer) SetInstaller(ctx context.Context, req *types.InstallerV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.SetInstaller(ctx, req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) SetUIConfig(ctx context.Context, req *types.UIConfigV1) (*emptypb.Empty, error) {
|
|
// TODO (avatus) send an audit event when SetUIConfig is called
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.SetUIConfig(ctx, req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) GetUIConfig(ctx context.Context, _ *emptypb.Empty) (*types.UIConfigV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
uiconfig, err := auth.ServerWithRoles.GetUIConfig(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
uiconfigv1, ok := uiconfig.(*types.UIConfigV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected type %T", uiconfig)
|
|
}
|
|
return uiconfigv1, nil
|
|
}
|
|
|
|
func (g *GRPCServer) DeleteUIConfig(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteUIConfig(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) defaultInstaller(ctx context.Context) (*types.InstallerV1, error) {
|
|
_, err := g.AuthServer.GetAutoUpdateAgentRollout(ctx)
|
|
switch {
|
|
case trace.IsNotFound(err):
|
|
return installer.LegacyDefaultInstaller, nil
|
|
case err != nil:
|
|
return nil, trace.Wrap(err, "failed to get query autoupdate state to build installer")
|
|
default:
|
|
return installer.NewDefaultInstaller, nil
|
|
}
|
|
}
|
|
|
|
// GetInstaller retrieves the installer script resource
|
|
func (g *GRPCServer) GetInstaller(ctx context.Context, req *types.ResourceRequest) (*types.InstallerV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
res, err := auth.GetInstaller(ctx, req.Name)
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
switch req.Name {
|
|
case installers.InstallerScriptName:
|
|
return g.defaultInstaller(ctx)
|
|
case installers.InstallerScriptNameAgentless:
|
|
return installers.DefaultAgentlessInstaller, nil
|
|
}
|
|
}
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
inst, ok := res.(*types.InstallerV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected installer type %T", res)
|
|
}
|
|
return inst, nil
|
|
}
|
|
|
|
// GetInstallers returns all installer script resources registered in the cluster.
|
|
func (g *GRPCServer) GetInstallers(ctx context.Context, _ *emptypb.Empty) (*types.InstallerV1List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
res, err := auth.GetInstallers(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
var installersV1 []*types.InstallerV1
|
|
|
|
defaultInstaller, err := g.defaultInstaller(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
defaultInstallers := map[string]*types.InstallerV1{
|
|
types.DefaultInstallerScriptName: defaultInstaller,
|
|
installers.InstallerScriptNameAgentless: installers.DefaultAgentlessInstaller,
|
|
}
|
|
|
|
for _, inst := range res {
|
|
instV1, ok := inst.(*types.InstallerV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unsupported installer type %T", inst)
|
|
}
|
|
delete(defaultInstallers, inst.GetName())
|
|
installersV1 = append(installersV1, instV1)
|
|
}
|
|
|
|
for _, inst := range defaultInstallers {
|
|
installersV1 = append(installersV1, inst)
|
|
}
|
|
|
|
return &types.InstallerV1List{
|
|
Installers: installersV1,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteInstaller sets the installer script resource to its default
|
|
func (g *GRPCServer) DeleteInstaller(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteInstaller(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteALlInstallers deletes all the installers
|
|
func (g *GRPCServer) DeleteAllInstallers(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteAllInstallers(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpdateConnectionDiagnostic updates a connection diagnostic
|
|
func (g *GRPCServer) UpdateConnectionDiagnostic(ctx context.Context, connectionDiagnostic *types.ConnectionDiagnosticV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.ServerWithRoles.UpdateConnectionDiagnostic(ctx, connectionDiagnostic); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// AppendDiagnosticTrace updates a connection diagnostic
|
|
func (g *GRPCServer) AppendDiagnosticTrace(ctx context.Context, in *authpb.AppendDiagnosticTraceRequest) (*types.ConnectionDiagnosticV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
connectionDiagnostic, err := auth.ServerWithRoles.AppendDiagnosticTrace(ctx, in.Name, in.Trace)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
connectionDiagnosticV1, ok := connectionDiagnostic.(*types.ConnectionDiagnosticV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected connection diagnostic type %T", connectionDiagnostic)
|
|
}
|
|
|
|
return connectionDiagnosticV1, nil
|
|
}
|
|
|
|
// GetKubernetesCluster returns the specified kubernetes cluster resource.
|
|
func (g *GRPCServer) GetKubernetesCluster(ctx context.Context, req *types.ResourceRequest) (*types.KubernetesClusterV3, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
kubeCluster, err := auth.GetKubernetesCluster(ctx, req.Name)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
kubeClusterV3, ok := kubeCluster.(*types.KubernetesClusterV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unsupported kubernetes cluster type %T", kubeCluster)
|
|
}
|
|
return kubeClusterV3, nil
|
|
}
|
|
|
|
// CreateKubernetesCluster creates a new kubernetes cluster resource.
|
|
func (g *GRPCServer) CreateKubernetesCluster(ctx context.Context, cluster *types.KubernetesClusterV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// if origin is not set, force it to be dynamic.
|
|
if len(cluster.Origin()) == 0 {
|
|
cluster.SetOrigin(types.OriginDynamic)
|
|
}
|
|
if err := auth.CreateKubernetesCluster(ctx, cluster); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// UpdateKubernetesCluster updates existing kubernetes cluster resource.
|
|
func (g *GRPCServer) UpdateKubernetesCluster(ctx context.Context, cluster *types.KubernetesClusterV3) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
// if origin is not set, force it to be dynamic.
|
|
if len(cluster.Origin()) == 0 {
|
|
cluster.SetOrigin(types.OriginDynamic)
|
|
}
|
|
if err := auth.UpdateKubernetesCluster(ctx, cluster); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetKubernetesClusters returns all kubernetes cluster resources.
|
|
func (g *GRPCServer) GetKubernetesClusters(ctx context.Context, _ *emptypb.Empty) (*types.KubernetesClusterV3List, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
clusters, err := auth.GetKubernetesClusters(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
kubeClusters := make([]*types.KubernetesClusterV3, 0, len(clusters))
|
|
for _, cluster := range clusters {
|
|
clusterV3, ok := cluster.(*types.KubernetesClusterV3)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unsupported kube cluster type %T", cluster)
|
|
}
|
|
kubeClusters = append(kubeClusters, clusterV3)
|
|
}
|
|
return &types.KubernetesClusterV3List{
|
|
KubernetesClusters: kubeClusters,
|
|
}, nil
|
|
}
|
|
|
|
// DeleteKubernetesCluster removes the specified kubernetes cluster.
|
|
func (g *GRPCServer) DeleteKubernetesCluster(ctx context.Context, req *types.ResourceRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteKubernetesCluster(ctx, req.Name); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteAllKubernetesClusters removes all kubernetes cluster.
|
|
func (g *GRPCServer) DeleteAllKubernetesClusters(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.DeleteAllKubernetesClusters(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
func (g *GRPCServer) ChangePassword(ctx context.Context, req *authpb.ChangePasswordRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.ChangePassword(ctx, req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// SubmitUsageEvent submits an external usage event.
|
|
func (g *GRPCServer) SubmitUsageEvent(ctx context.Context, req *authpb.SubmitUsageEventRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if err := auth.SubmitUsageEvent(ctx, req); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetLicense returns the license used to start the auth server.
|
|
func (g *GRPCServer) GetLicense(ctx context.Context, req *authpb.GetLicenseRequest) (*authpb.GetLicenseResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
license, err := auth.GetLicense(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &authpb.GetLicenseResponse{
|
|
License: []byte(license),
|
|
}, nil
|
|
}
|
|
|
|
// ListReleases returns a list of Teleport Enterprise releases.
|
|
func (g *GRPCServer) ListReleases(ctx context.Context, req *authpb.ListReleasesRequest) (*authpb.ListReleasesResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
releases, err := auth.ListReleases(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &authpb.ListReleasesResponse{
|
|
Releases: releases,
|
|
}, nil
|
|
}
|
|
|
|
// ListSAMLIdPServiceProviders returns a paginated list of SAML IdP service provider resources.
|
|
func (g *GRPCServer) ListSAMLIdPServiceProviders(ctx context.Context, req *authpb.ListSAMLIdPServiceProvidersRequest) (*authpb.ListSAMLIdPServiceProvidersResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
serviceProviders, nextKey, err := auth.ListSAMLIdPServiceProviders(ctx, int(req.GetLimit()), req.GetNextKey())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
serviceProvidersV1 := make([]*types.SAMLIdPServiceProviderV1, len(serviceProviders))
|
|
for i, sp := range serviceProviders {
|
|
v1, ok := sp.(*types.SAMLIdPServiceProviderV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected SAML IdP service provider type %T", sp)
|
|
}
|
|
serviceProvidersV1[i] = v1
|
|
}
|
|
|
|
return &authpb.ListSAMLIdPServiceProvidersResponse{
|
|
ServiceProviders: serviceProvidersV1,
|
|
NextKey: nextKey,
|
|
}, nil
|
|
}
|
|
|
|
// GetSAMLIdPServiceProvider returns the specified SAML IdP service provider resources.
|
|
func (g *GRPCServer) GetSAMLIdPServiceProvider(ctx context.Context, req *authpb.GetSAMLIdPServiceProviderRequest) (*types.SAMLIdPServiceProviderV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sp, err := auth.GetSAMLIdPServiceProvider(ctx, req.GetName())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
serviceProviderV1, ok := sp.(*types.SAMLIdPServiceProviderV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected SAML IdP service provider type %T", sp)
|
|
}
|
|
|
|
return serviceProviderV1, nil
|
|
}
|
|
|
|
// CreateSAMLIdPServiceProvider creates a new SAML IdP service provider resource.
|
|
func (g *GRPCServer) CreateSAMLIdPServiceProvider(ctx context.Context, sp *types.SAMLIdPServiceProviderV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, trace.Wrap(auth.CreateSAMLIdPServiceProvider(ctx, sp))
|
|
}
|
|
|
|
// UpdateSAMLIdPServiceProvider updates an existing SAML IdP service provider resource.
|
|
func (g *GRPCServer) UpdateSAMLIdPServiceProvider(ctx context.Context, sp *types.SAMLIdPServiceProviderV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, trace.Wrap(auth.UpdateSAMLIdPServiceProvider(ctx, sp))
|
|
}
|
|
|
|
// DeleteSAMLIdPServiceProvider removes the specified SAML IdP service provider resource.
|
|
func (g *GRPCServer) DeleteSAMLIdPServiceProvider(ctx context.Context, req *authpb.DeleteSAMLIdPServiceProviderRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, trace.Wrap(auth.DeleteSAMLIdPServiceProvider(ctx, req.GetName()))
|
|
}
|
|
|
|
// DeleteAllSAMLIdPServiceProviders removes all SAML IdP service providers.
|
|
func (g *GRPCServer) DeleteAllSAMLIdPServiceProviders(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, trace.Wrap(auth.DeleteAllSAMLIdPServiceProviders(ctx))
|
|
}
|
|
|
|
// ListUserGroups returns a paginated list of user group resources.
|
|
func (g *GRPCServer) ListUserGroups(ctx context.Context, req *authpb.ListUserGroupsRequest) (*authpb.ListUserGroupsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
userGroups, nextKey, err := auth.ListUserGroups(ctx, int(req.GetLimit()), req.GetNextKey())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
userGroupsV1 := make([]*types.UserGroupV1, len(userGroups))
|
|
for i, g := range userGroups {
|
|
v1, ok := g.(*types.UserGroupV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected user group type %T", g)
|
|
}
|
|
userGroupsV1[i] = v1
|
|
}
|
|
|
|
return &authpb.ListUserGroupsResponse{
|
|
UserGroups: userGroupsV1,
|
|
NextKey: nextKey,
|
|
}, nil
|
|
}
|
|
|
|
// GetUserGroup returns the specified user group resources.
|
|
func (g *GRPCServer) GetUserGroup(ctx context.Context, req *authpb.GetUserGroupRequest) (*types.UserGroupV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
sp, err := auth.GetUserGroup(ctx, req.GetName())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
serviceProviderV1, ok := sp.(*types.UserGroupV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected user group type %T", sp)
|
|
}
|
|
|
|
return serviceProviderV1, nil
|
|
}
|
|
|
|
// CreateUserGroup creates a new user group resource.
|
|
func (g *GRPCServer) CreateUserGroup(ctx context.Context, sp *types.UserGroupV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, trace.Wrap(auth.CreateUserGroup(ctx, sp))
|
|
}
|
|
|
|
// UpdateUserGroup updates an existing user group resource.
|
|
func (g *GRPCServer) UpdateUserGroup(ctx context.Context, sp *types.UserGroupV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, trace.Wrap(auth.UpdateUserGroup(ctx, sp))
|
|
}
|
|
|
|
// DeleteUserGroup removes the specified user group resource.
|
|
func (g *GRPCServer) DeleteUserGroup(ctx context.Context, req *authpb.DeleteUserGroupRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, trace.Wrap(auth.DeleteUserGroup(ctx, req.GetName()))
|
|
}
|
|
|
|
// DeleteAllUserGroups removes all user groups.
|
|
func (g *GRPCServer) DeleteAllUserGroups(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &emptypb.Empty{}, trace.Wrap(auth.DeleteAllUserGroups(ctx))
|
|
}
|
|
|
|
// UpdateHeadlessAuthenticationState updates a headless authentication state.
|
|
func (g *GRPCServer) UpdateHeadlessAuthenticationState(ctx context.Context, req *authpb.UpdateHeadlessAuthenticationStateRequest) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return &emptypb.Empty{}, trace.Wrap(err)
|
|
}
|
|
|
|
err = auth.UpdateHeadlessAuthenticationState(ctx, req.Id, req.State, req.MfaResponse)
|
|
return &emptypb.Empty{}, trace.Wrap(err)
|
|
}
|
|
|
|
// GetHeadlessAuthentication retrieves a headless authentication.
|
|
func (g *GRPCServer) GetHeadlessAuthentication(ctx context.Context, req *authpb.GetHeadlessAuthenticationRequest) (*types.HeadlessAuthentication, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// First, try to retrieve the headless authentication directly if it already exists.
|
|
if ha, err := auth.GetHeadlessAuthentication(ctx, req.Id); err == nil {
|
|
return ha, nil
|
|
} else if !trace.IsNotFound(err) {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// If the headless authentication doesn't exist yet, the headless login process may be waiting
|
|
// for the user to create a stub to authorize the insert.
|
|
if err := auth.UpsertHeadlessAuthenticationStub(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// Force a short request timeout to prevent GetHeadlessAuthenticationFromWatcher
|
|
// from waiting indefinitely for a nonexistent headless authentication. This is
|
|
// useful for cases when the headless link/command is copied incorrectly or is
|
|
// run with the wrong user.
|
|
timeout := 5 * time.Second
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
|
|
// Wait for the login process to insert the actual headless authentication details.
|
|
authReq, err := auth.GetHeadlessAuthenticationFromWatcher(ctx, req.Id)
|
|
return authReq, trace.Wrap(err)
|
|
}
|
|
|
|
// WatchPendingHeadlessAuthentications watches the backend for pending headless authentication requests for the user.
|
|
func (g *GRPCServer) WatchPendingHeadlessAuthentications(_ *emptypb.Empty, stream authpb.AuthService_WatchPendingHeadlessAuthenticationsServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
watcher, err := auth.WatchPendingHeadlessAuthentications(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
defer watcher.Close()
|
|
|
|
stubErr := make(chan error)
|
|
go func() {
|
|
stubErr <- auth.MaintainHeadlessAuthenticationStub(stream.Context())
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case err := <-stubErr:
|
|
return trace.Wrap(err)
|
|
case <-stream.Context().Done():
|
|
return nil
|
|
case <-watcher.Done():
|
|
return watcher.Error()
|
|
case event := <-watcher.Events():
|
|
out, err := client.EventToGRPC(event)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
size := float64(proto.Size(out))
|
|
watcherEventsEmitted.WithLabelValues(resourceLabel(event)).Observe(size)
|
|
watcherEventSizes.Observe(size)
|
|
|
|
if err := stream.Send(out); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ExportUpgradeWindows is used to load derived upgrade window values for agents that
|
|
// need to export schedules to external upgraders.
|
|
func (g *GRPCServer) ExportUpgradeWindows(ctx context.Context, req *authpb.ExportUpgradeWindowsRequest) (*authpb.ExportUpgradeWindowsResponse, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rsp, err := auth.ExportUpgradeWindows(ctx, *req)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &rsp, nil
|
|
}
|
|
|
|
// GetClusterMaintenanceConfig gets the current maintenance config singleton.
|
|
func (g *GRPCServer) GetClusterMaintenanceConfig(ctx context.Context, _ *emptypb.Empty) (*types.ClusterMaintenanceConfigV1, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
cmc, err := auth.GetClusterMaintenanceConfig(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rsp, ok := cmc.(*types.ClusterMaintenanceConfigV1)
|
|
if !ok {
|
|
return nil, trace.BadParameter("unexpected maintenance config type %T", cmc)
|
|
}
|
|
|
|
return rsp, nil
|
|
}
|
|
|
|
// UpdateClusterMaintenanceConfig updates the current maintenance config singleton.
|
|
func (g *GRPCServer) UpdateClusterMaintenanceConfig(ctx context.Context, cmc *types.ClusterMaintenanceConfigV1) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.UpdateClusterMaintenanceConfig(ctx, cmc); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// DeleteClusterMaintenanceConfig deletes the current maintenance config singleton.
|
|
func (g *GRPCServer) DeleteClusterMaintenanceConfig(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := auth.DeleteClusterMaintenanceConfig(ctx); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &emptypb.Empty{}, nil
|
|
}
|
|
|
|
// GetBackend returns the backend from the underlying auth server.
|
|
func (g *GRPCServer) GetBackend() backend.Backend {
|
|
return g.AuthServer.bk
|
|
}
|
|
|
|
// GRPCServerConfig specifies gRPC server configuration
|
|
type GRPCServerConfig struct {
|
|
// APIConfig is gRPC server API configuration
|
|
APIConfig
|
|
// TLS is gRPC server config
|
|
TLS *tls.Config
|
|
// Middleware is the request TLS client authenticator
|
|
Middleware *Middleware
|
|
// UnaryInterceptors is the gRPC unary interceptor chain.
|
|
UnaryInterceptors []grpc.UnaryServerInterceptor
|
|
// StreamInterceptors is the gRPC stream interceptor chain.
|
|
StreamInterceptors []grpc.StreamServerInterceptor
|
|
}
|
|
|
|
// CheckAndSetDefaults checks and sets default values
|
|
func (cfg *GRPCServerConfig) CheckAndSetDefaults() error {
|
|
if cfg.TLS == nil {
|
|
return trace.BadParameter("missing parameter TLS")
|
|
}
|
|
if cfg.UnaryInterceptors == nil {
|
|
return trace.BadParameter("missing parameter UnaryInterceptors")
|
|
}
|
|
if cfg.StreamInterceptors == nil {
|
|
return trace.BadParameter("missing parameter StreamInterceptors")
|
|
}
|
|
if cfg.Middleware == nil {
|
|
return trace.BadParameter("missing parameter Middleware")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NewGRPCServer returns a new instance of gRPC server
|
|
func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) {
|
|
err := metrics.RegisterPrometheusCollectors(heartbeatConnectionsReceived, watcherEventsEmitted, watcherEventSizes, connectedResources)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
if err := cfg.CheckAndSetDefaults(); err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
logger := slog.With(teleport.ComponentKey, teleport.Component(teleport.ComponentAuth, teleport.ComponentGRPC))
|
|
|
|
logger.DebugContext(context.Background(), "creating gRPC server",
|
|
"keep_alive_period", cfg.KeepAlivePeriod,
|
|
"keep_alive_count", cfg.KeepAliveCount,
|
|
)
|
|
|
|
// httplib.TLSCreds are explicitly used instead of credentials.NewTLS because the latter
|
|
// modifies the tls.Config.NextProtos which causes problems due to multiplexing on the auth
|
|
// listener.
|
|
creds, err := NewTransportCredentials(TransportCredentialsConfig{
|
|
TransportCredentials: &httplib.TLSCreds{Config: cfg.TLS},
|
|
UserGetter: cfg.Middleware,
|
|
GetAuthPreference: cfg.AuthServer.Cache.GetAuthPreference,
|
|
Clock: cfg.AuthServer.clock,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
server := grpc.NewServer(
|
|
grpc.Creds(creds),
|
|
grpc.ChainUnaryInterceptor(cfg.UnaryInterceptors...),
|
|
grpc.ChainStreamInterceptor(cfg.StreamInterceptors...),
|
|
grpc.KeepaliveParams(
|
|
keepalive.ServerParameters{
|
|
Time: cfg.KeepAlivePeriod,
|
|
Timeout: cfg.KeepAlivePeriod * time.Duration(cfg.KeepAliveCount),
|
|
},
|
|
),
|
|
grpc.KeepaliveEnforcementPolicy(
|
|
keepalive.EnforcementPolicy{
|
|
MinTime: cfg.KeepAlivePeriod,
|
|
PermitWithoutStream: true,
|
|
},
|
|
),
|
|
grpc.MaxConcurrentStreams(defaults.GRPCMaxConcurrentStreams),
|
|
)
|
|
|
|
usersService, err := usersv1.NewService(usersv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Backend: cfg.AuthServer.Services,
|
|
Emitter: cfg.Emitter,
|
|
Reporter: cfg.AuthServer.Services.UsageReporter,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "users.service"),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
usersv1pb.RegisterUsersServiceServer(server, usersService)
|
|
|
|
presenceService, err := presencev1.NewService(presencev1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
AuthServer: cfg.AuthServer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Emitter: cfg.Emitter,
|
|
Reporter: cfg.AuthServer.Services.UsageReporter,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "presence.service"),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
presencev1pb.RegisterPresenceServiceServer(server, presenceService)
|
|
|
|
botService, err := machineidv1.NewBotService(machineidv1.BotServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Backend: cfg.AuthServer.Services,
|
|
Reporter: cfg.AuthServer.Services.UsageReporter,
|
|
Emitter: cfg.Emitter,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "bot.service"),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating bot service")
|
|
}
|
|
machineidv1pb.RegisterBotServiceServer(server, botService)
|
|
|
|
botInstanceService, err := machineidv1.NewBotInstanceService(machineidv1.BotInstanceServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services.BotInstance,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating bot instance service")
|
|
}
|
|
machineidv1pb.RegisterBotInstanceServiceServer(server, botInstanceService)
|
|
|
|
workloadIdentityService, err := machineidv1.NewWorkloadIdentityService(machineidv1.WorkloadIdentityServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Reporter: cfg.AuthServer.Services.UsageReporter,
|
|
Emitter: cfg.Emitter,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
KeyStore: cfg.AuthServer.keyStore,
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "workload-identity.service"),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating workload identity service")
|
|
}
|
|
machineidv1pb.RegisterWorkloadIdentityServiceServer(server, workloadIdentityService)
|
|
|
|
spiffeFederationService, err := machineidv1.NewSPIFFEFederationService(machineidv1.SPIFFEFederationServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services.SPIFFEFederations,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
Emitter: cfg.Emitter,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating SPIFFE federation service")
|
|
}
|
|
machineidv1pb.RegisterSPIFFEFederationServiceServer(server, spiffeFederationService)
|
|
|
|
workloadIdentityResourceService, err := workloadidentityv1.NewResourceService(&workloadidentityv1.ResourceServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services.WorkloadIdentities,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Emitter: cfg.Emitter,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating workload identity resource service")
|
|
}
|
|
workloadidentityv1pb.RegisterWorkloadIdentityResourceServiceServer(server, workloadIdentityResourceService)
|
|
|
|
clusterName, err := cfg.AuthServer.GetClusterName(cfg.AuthServer.CloseContext())
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "getting cluster name")
|
|
}
|
|
workloadIdentityIssuanceService, err := workloadidentityv1.NewIssuanceService(&workloadidentityv1.IssuanceServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Emitter: cfg.Emitter,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
KeyStore: cfg.AuthServer.keyStore,
|
|
OverrideGetter: cfg.AuthServer,
|
|
ClusterName: clusterName.GetClusterName(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating workload identity issuance service")
|
|
}
|
|
workloadidentityv1pb.RegisterWorkloadIdentityIssuanceServiceServer(server, workloadIdentityIssuanceService)
|
|
|
|
revSvcConfig := &workloadidentityv1.RevocationServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Emitter: cfg.Emitter,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
Store: cfg.AuthServer.Services.WorkloadIdentityX509Revocations,
|
|
KeyStore: cfg.AuthServer.keyStore,
|
|
CertAuthorityGetter: cfg.AuthServer.Cache,
|
|
EventsWatcher: cfg.AuthServer.Services,
|
|
ClusterName: clusterName.GetClusterName(),
|
|
}
|
|
if cfg.MutateRevocationsServiceConfig != nil {
|
|
cfg.MutateRevocationsServiceConfig(revSvcConfig)
|
|
}
|
|
workloadIdentityRevocationService, err := workloadidentityv1.NewRevocationService(revSvcConfig)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating workload identity revocation service")
|
|
}
|
|
workloadidentityv1pb.RegisterWorkloadIdentityRevocationServiceServer(server, workloadIdentityRevocationService)
|
|
go workloadIdentityRevocationService.RunCRLSigner(cfg.AuthServer.CloseContext())
|
|
|
|
if cfg.PluginRegistry == nil || !cfg.PluginRegistry.IsRegistered("auth.enterprise") {
|
|
srv, err := workloadidentityv1.NewX509OverridesService(workloadidentityv1.X509OverridesServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Storage: cfg.AuthServer.Services,
|
|
Emitter: cfg.Emitter,
|
|
|
|
ClusterName: clusterName.GetClusterName(),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating workload identity X509 overrides service")
|
|
}
|
|
workloadidentityv1pb.RegisterX509OverridesServiceServer(server, srv)
|
|
workloadidentityv1pb.RegisterSigstorePolicyResourceServiceServer(server, workloadidentityv1.NewSigstorePolicyResourceService())
|
|
}
|
|
|
|
dbObjectImportRuleService, err := dbobjectimportrulev1.NewDatabaseObjectImportRuleService(dbobjectimportrulev1.DatabaseObjectImportRuleServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "db_obj_import_rule"),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating database objectImportRule service")
|
|
}
|
|
dbobjectimportrulev1pb.RegisterDatabaseObjectImportRuleServiceServer(server, dbObjectImportRuleService)
|
|
|
|
dbObjectService, err := dbobjectv1.NewDatabaseObjectService(dbobjectv1.DatabaseObjectServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "db_object"),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating database object service")
|
|
}
|
|
dbobjectv1pb.RegisterDatabaseObjectServiceServer(server, dbObjectService)
|
|
|
|
stableUNIXUsersServiceServer, err := stableunixusers.New(stableunixusers.Config{
|
|
Authorizer: cfg.Authorizer,
|
|
Emitter: cfg.Emitter,
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "stable_unix_users"),
|
|
|
|
Backend: cfg.AuthServer.bk,
|
|
ReadOnlyCache: cfg.AuthServer.ReadOnlyCache,
|
|
|
|
StableUNIXUsers: cfg.AuthServer.Services.StableUNIXUsersInternal,
|
|
ClusterConfiguration: cfg.AuthServer.Services.ClusterConfigurationInternal,
|
|
|
|
CacheClock: cfg.AuthServer.clock,
|
|
CacheContext: cfg.AuthServer.closeCtx,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err, "creating stable UNIX user service")
|
|
}
|
|
stableunixusersv1.RegisterStableUNIXUsersServiceServer(
|
|
server,
|
|
stableUNIXUsersServiceServer,
|
|
)
|
|
|
|
authServer := &GRPCServer{
|
|
APIConfig: cfg.APIConfig,
|
|
logger: logger,
|
|
server: server,
|
|
}
|
|
|
|
if en := os.Getenv("TELEPORT_UNSTABLE_CREATEAUDITSTREAM_INFLIGHT_LIMIT"); en != "" {
|
|
inflightLimit, err := strconv.ParseInt(en, 10, 64)
|
|
if err != nil {
|
|
logger.ErrorContext(context.Background(), "Failed to parse the TELEPORT_UNSTABLE_CREATEAUDITSTREAM_INFLIGHT_LIMIT envvar, limit will not be enforced")
|
|
inflightLimit = -1
|
|
}
|
|
if inflightLimit == 0 {
|
|
logger.WarnContext(context.Background(), "TELEPORT_UNSTABLE_CREATEAUDITSTREAM_INFLIGHT_LIMIT is set to 0, no CreateAuditStream RPCs will be allowed")
|
|
}
|
|
metrics.RegisterPrometheusCollectors(
|
|
createAuditStreamAcceptedTotalMetric,
|
|
createAuditStreamRejectedTotalMetric,
|
|
createAuditStreamLimitMetric,
|
|
)
|
|
createAuditStreamLimitMetric.Set(float64(inflightLimit))
|
|
if inflightLimit >= 0 {
|
|
authServer.createAuditStreamSemaphore = make(chan struct{}, inflightLimit)
|
|
}
|
|
}
|
|
|
|
authpb.RegisterAuthServiceServer(server, authServer)
|
|
collectortracepb.RegisterTraceServiceServer(server, authServer)
|
|
auditlogpb.RegisterAuditLogServiceServer(server, authServer)
|
|
|
|
dynamicWindows, err := dynamicwindowsv1.NewService(dynamicwindowsv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Cache: cfg.AuthServer.Cache,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
dynamicwindowsv1pb.RegisterDynamicWindowsServiceServer(server, dynamicWindows)
|
|
|
|
trust, err := trustv1.NewService(&trustv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Backend: cfg.AuthServer.Services,
|
|
AuthServer: cfg.AuthServer,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
trustv1pb.RegisterTrustServiceServer(server, trust)
|
|
|
|
joinServiceServer := joinserver.NewJoinServiceGRPCServer(cfg.AuthServer)
|
|
authpb.RegisterJoinServiceServer(server, joinServiceServer)
|
|
|
|
integrationServiceServer, err := integrationv1.NewService(&integrationv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Cache: cfg.AuthServer.Cache,
|
|
KeyStoreManager: cfg.AuthServer.GetKeyStore(),
|
|
Clock: cfg.AuthServer.clock,
|
|
Emitter: cfg.Emitter,
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "integrations.service"),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
integrationv1pb.RegisterIntegrationServiceServer(server, integrationServiceServer)
|
|
|
|
integrationAWSOIDCServiceServer, err := integrationv1.NewAWSOIDCService(&integrationv1.AWSOIDCServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
IntegrationService: integrationServiceServer,
|
|
Cache: cfg.AuthServer,
|
|
ProxyPublicAddrGetter: cfg.AuthServer.getProxyPublicAddr,
|
|
Clock: cfg.AuthServer.clock,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
integrationv1pb.RegisterAWSOIDCServiceServer(server, integrationAWSOIDCServiceServer)
|
|
|
|
userTask, err := usertasksv1.NewService(usertasksv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Cache: cfg.AuthServer.Cache,
|
|
// This must be a function because cfg.AuthServer.UsageReporter is changed after `NewGRPCServer` is called.
|
|
// It starts as a DiscardUsageReporter, but when running in Cloud, gets replaced by a real reporter.
|
|
UsageReporter: func() usagereporter.UsageReporter { return cfg.AuthServer.UsageReporter },
|
|
Emitter: cfg.Emitter,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
usertaskv1pb.RegisterUserTaskServiceServer(server, userTask)
|
|
|
|
discoveryConfig, err := discoveryconfigv1.NewService(discoveryconfigv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Clock: cfg.AuthServer.clock,
|
|
Emitter: cfg.Emitter,
|
|
Logger: cfg.AuthServer.logger.With(teleport.ComponentKey, "discoveryconfig_crud_service"),
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
discoveryconfigv1pb.RegisterDiscoveryConfigServiceServer(server, discoveryConfig)
|
|
|
|
crownJewel, err := crownjewelv1.NewService(crownjewelv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Reader: cfg.AuthServer.Cache,
|
|
Emitter: cfg.Emitter,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
crownjewelv1pb.RegisterCrownJewelServiceServer(server, crownJewel)
|
|
|
|
// Initialize and register the user preferences service.
|
|
userPreferencesSrv, err := userpreferencesv1.NewService(&userpreferencesv1.ServiceConfig{
|
|
Backend: cfg.AuthServer.Services,
|
|
Authorizer: cfg.Authorizer,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
userpreferencesv1pb.RegisterUserPreferencesServiceServer(server, userPreferencesSrv)
|
|
|
|
// Initialize and register the user login state service.
|
|
userLoginState, err := local.NewUserLoginStateService(cfg.AuthServer.bk)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
userLoginStateServer, err := userloginstatev1.NewService(userloginstatev1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
UserLoginStates: userLoginState,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
userloginstatev1pb.RegisterUserLoginStateServiceServer(server, userLoginStateServer)
|
|
|
|
clusterConfigService, err := clusterconfigv1.NewService(clusterconfigv1.ServiceConfig{
|
|
Cache: cfg.AuthServer.Cache,
|
|
Backend: cfg.AuthServer.Services,
|
|
Authorizer: cfg.Authorizer,
|
|
Emitter: cfg.Emitter,
|
|
AccessGraph: clusterconfigv1.AccessGraphConfig{
|
|
Enabled: cfg.APIConfig.AccessGraph.Enabled,
|
|
CA: cfg.APIConfig.AccessGraph.CA,
|
|
Address: cfg.APIConfig.AccessGraph.Address,
|
|
Insecure: cfg.APIConfig.AccessGraph.Insecure,
|
|
},
|
|
ReadOnlyCache: cfg.AuthServer.ReadOnlyCache,
|
|
SignatureAlgorithmSuiteParams: types.SignatureAlgorithmSuiteParams{
|
|
FIPS: cfg.AuthServer.fips,
|
|
UsingHSMOrKMS: cfg.AuthServer.keyStore.UsingHSMOrKMS(),
|
|
Cloud: modules.GetModules().Features().Cloud,
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
clusterconfigv1pb.RegisterClusterConfigServiceServer(server, clusterConfigService)
|
|
|
|
// Initialize and register the Kubernetes waiting container service.
|
|
kubeWaitingContsServer, err := kubewaitingcontainerv1.NewService(kubewaitingcontainerv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Cache: cfg.AuthServer.Cache,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
kubewaitingcontainerv1pb.RegisterKubeWaitingContainersServiceServer(server, kubeWaitingContsServer)
|
|
|
|
accessMonitoringRuleServer, err := accessmonitoringrulesv1.NewService(&accessmonitoringrulesv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Cache: cfg.AuthServer.Cache,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
accessmonitoringrules.RegisterAccessMonitoringRulesServiceServer(server, accessMonitoringRuleServer)
|
|
|
|
// Initialize and register the notifications service.
|
|
notificationsServer, err := notificationsv1.NewService(notificationsv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
Clock: cfg.AuthServer.GetClock(),
|
|
UserNotificationCache: cfg.AuthServer.UserNotificationCache,
|
|
GlobalNotificationCache: cfg.AuthServer.GlobalNotificationCache,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
notificationsv1pb.RegisterNotificationServiceServer(server, notificationsServer)
|
|
|
|
vnetConfigStorage, err := local.NewVnetConfigService(cfg.AuthServer.bk)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
vnetConfigServiceServer := vnetconfigv1.NewService(vnetConfigStorage, cfg.Authorizer)
|
|
vnetv1pb.RegisterVnetConfigServiceServer(server, vnetConfigServiceServer)
|
|
|
|
staticHostUserServer, err := userprovisioningv2.NewService(userprovisioningv2.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Emitter: cfg.Emitter,
|
|
Backend: cfg.AuthServer.Services,
|
|
Cache: cfg.AuthServer.Cache,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
userprovisioningv2pb.RegisterStaticHostUsersServiceServer(server, staticHostUserServer)
|
|
|
|
autoUpdateServiceServer, err := autoupdatev1.NewService(autoupdatev1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Emitter: cfg.Emitter,
|
|
Backend: cfg.AuthServer.Services,
|
|
Cache: cfg.AuthServer.Cache,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
autoupdatev1pb.RegisterAutoUpdateServiceServer(server, autoUpdateServiceServer)
|
|
|
|
identityCenterService, err := local.NewIdentityCenterService(local.IdentityCenterServiceConfig{
|
|
Backend: cfg.AuthServer.bk,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
identitycenterv1.RegisterIdentityCenterServiceServer(server, identityCenterService)
|
|
|
|
provisioningStateService, err := local.NewProvisioningStateService(cfg.AuthServer.bk)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
provisioningv1.RegisterProvisioningServiceServer(server, provisioningStateService)
|
|
|
|
gitServerService, err := gitserverv1.NewService(gitserverv1.Config{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services,
|
|
ProxyPublicAddrGetter: cfg.AuthServer.getProxyPublicAddr,
|
|
GitHubAuthRequestCreator: cfg.AuthServer.CreateGithubAuthRequest,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
gitserverv1pb.RegisterGitServerServiceServer(server, gitServerService)
|
|
|
|
// Only register the service if this is an open source build. Enterprise builds
|
|
// register the actual service via an auth plugin, if we register here then all
|
|
// Enterprise builds would fail with a duplicate service registered error.
|
|
if cfg.PluginRegistry == nil || !cfg.PluginRegistry.IsRegistered("auth.enterprise") {
|
|
loginrulev1pb.RegisterLoginRuleServiceServer(server, loginrulev1.NotImplementedService{})
|
|
}
|
|
|
|
decisionService, err := decisionv1.NewService(decisionv1.ServiceConfig{
|
|
DecisionService: cfg.AuthServer.pdp,
|
|
Authorizer: cfg.Authorizer,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
decisionpb.RegisterDecisionServiceServer(server, decisionService)
|
|
|
|
healthCheckConfigSvc, err := healthcheckconfigv1.NewService(healthcheckconfigv1.ServiceConfig{
|
|
Authorizer: cfg.Authorizer,
|
|
Backend: cfg.AuthServer.Services.HealthCheckConfig,
|
|
Cache: cfg.AuthServer.Cache,
|
|
Emitter: cfg.Emitter,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
healthcheckconfigv1pb.RegisterHealthCheckConfigServiceServer(server, healthCheckConfigSvc)
|
|
|
|
return authServer, nil
|
|
}
|
|
|
|
type grpcContext struct {
|
|
*authz.Context
|
|
*ServerWithRoles
|
|
}
|
|
|
|
// authenticate extracts authentication context and returns initialized auth server
|
|
func (g *GRPCServer) authenticate(ctx context.Context) (*grpcContext, error) {
|
|
// HTTPS server expects auth context to be set by the auth middleware
|
|
authContext, err := g.Authorizer.Authorize(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return &grpcContext{
|
|
Context: authContext,
|
|
ServerWithRoles: &ServerWithRoles{
|
|
authServer: g.AuthServer,
|
|
context: *authContext,
|
|
alog: g.AuthServer,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// GetUnstructuredEvents searches for events on the backend and sends them back in an unstructured format.
|
|
func (g *GRPCServer) GetUnstructuredEvents(ctx context.Context, req *auditlogpb.GetUnstructuredEventsRequest) (*auditlogpb.EventsUnstructured, error) {
|
|
auth, err := g.authenticate(ctx)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
rawEvents, lastkey, err := auth.ServerWithRoles.SearchEvents(ctx, events.SearchEventsRequest{
|
|
From: req.StartDate.AsTime(),
|
|
To: req.EndDate.AsTime(),
|
|
EventTypes: req.EventTypes,
|
|
Limit: int(req.Limit),
|
|
Order: types.EventOrder(req.Order),
|
|
StartKey: req.StartKey,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
unstructuredEvents := make([]*auditlogpb.EventUnstructured, 0, len(rawEvents))
|
|
for _, event := range rawEvents {
|
|
unstructuredEvent, err := apievents.ToUnstructured(event)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
unstructuredEvents = append(unstructuredEvents, unstructuredEvent)
|
|
}
|
|
|
|
return &auditlogpb.EventsUnstructured{
|
|
Items: unstructuredEvents,
|
|
LastKey: lastkey,
|
|
}, nil
|
|
}
|
|
|
|
// ExportUnstructuredEvents exports events from a given event chunk returned by GetEventExportChunks. This API prioritizes
|
|
// performance over ordering and filtering, and is intended for bulk export of events.
|
|
func (g *GRPCServer) ExportUnstructuredEvents(req *auditlogpb.ExportUnstructuredEventsRequest, stream auditlogpb.AuditLogService_ExportUnstructuredEventsServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
events := auth.ServerWithRoles.ExportUnstructuredEvents(stream.Context(), req)
|
|
|
|
for events.Next() {
|
|
if err := stream.Send(events.Item()); err != nil {
|
|
events.Done()
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
return trace.Wrap(events.Done())
|
|
}
|
|
|
|
// GetEventExportChunks returns a stream of event chunks that can be exported via ExportUnstructuredEvents. The returned
|
|
// list isn't ordered and polling for new chunks requires re-consuming the entire stream from the beginning.
|
|
func (g *GRPCServer) GetEventExportChunks(req *auditlogpb.GetEventExportChunksRequest, stream auditlogpb.AuditLogService_GetEventExportChunksServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
chunks := auth.ServerWithRoles.GetEventExportChunks(stream.Context(), req)
|
|
|
|
for chunks.Next() {
|
|
if err := stream.Send(chunks.Item()); err != nil {
|
|
chunks.Done()
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
return trace.Wrap(chunks.Done())
|
|
}
|
|
|
|
// StreamUnstructuredSessionEvents streams all events from a given session recording as an unstructured format.
|
|
func (g *GRPCServer) StreamUnstructuredSessionEvents(req *auditlogpb.StreamUnstructuredSessionEventsRequest, stream auditlogpb.AuditLogService_StreamUnstructuredSessionEventsServer) error {
|
|
auth, err := g.authenticate(stream.Context())
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
c, e := auth.ServerWithRoles.StreamSessionEvents(stream.Context(), session.ID(req.SessionId), int64(req.StartIndex))
|
|
|
|
for {
|
|
select {
|
|
case event, more := <-c:
|
|
if !more {
|
|
return nil
|
|
}
|
|
// convert event to JSON
|
|
eventJson, err := apievents.ToUnstructured(event)
|
|
if err != nil {
|
|
return trail.ToGRPC(trace.Wrap(err))
|
|
}
|
|
if err := stream.Send(eventJson); err != nil {
|
|
return trail.ToGRPC(trace.Wrap(err))
|
|
}
|
|
case err := <-e:
|
|
return trail.ToGRPC(trace.Wrap(err))
|
|
}
|
|
}
|
|
}
|