Files
teleport/lib/auth/auth_with_roles.go
T
rosstimothy b0dc78610e Migrate upserting CAs to gRPC (#23140)
Adds `UpsertCertAuthority` to the `teleport.v1.trust.TrustService`
and deprecates the HTTP api. The `services.Trust` interface was
also updated to include a `context.Context` in both
`CreateCertAuthority` and `UpsertCertAuthority.

To ensure backwards compatibility the `auth.Client` will first
attempt to use the gRPC client and if that fails with a not
implemented error it will fallback to using HTTP.

Part of #6394
2023-03-16 14:46:43 +00:00

5792 lines
221 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Copyright 2015-2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package auth
import (
"context"
"fmt"
"net/url"
"strings"
"time"
"github.com/gravitational/trace"
"github.com/sirupsen/logrus"
collectortracev1 "go.opentelemetry.io/proto/otlp/collector/trace/v1"
otlpcommonv1 "go.opentelemetry.io/proto/otlp/common/v1"
"golang.org/x/exp/slices"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/client/okta"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1"
loginrulepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/loginrule/v1"
oktapb "github.com/gravitational/teleport/api/gen/proto/go/teleport/okta/v1"
pluginspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/plugins/v1"
samlidppb "github.com/gravitational/teleport/api/gen/proto/go/teleport/samlidp/v1"
trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1"
"github.com/gravitational/teleport/api/internalutils/stream"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/wrappers"
apiutils "github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/auth/trust/trustv1"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/defaults"
dtauthz "github.com/gravitational/teleport/lib/devicetrust/authz"
dtconfig "github.com/gravitational/teleport/lib/devicetrust/config"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/local"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/utils"
)
// ServerWithRoles is a wrapper around auth service
// methods that focuses on authorizing every request
type ServerWithRoles struct {
authServer *Server
alog events.AuditLogSessionStreamer
// context holds authorization context
context authz.Context
}
// CloseContext is closed when the auth server shuts down
func (a *ServerWithRoles) CloseContext() context.Context {
return a.authServer.closeCtx
}
func (a *ServerWithRoles) actionWithContext(ctx *services.Context, namespace, resource string, verbs ...string) error {
if len(verbs) == 0 {
return trace.BadParameter("no verbs provided for authorization check on resource %q", resource)
}
var errs []error
for _, verb := range verbs {
errs = append(errs, a.context.Checker.CheckAccessToRule(ctx, namespace, resource, verb, false))
}
// Convert generic aggregate error to AccessDenied.
if err := trace.NewAggregate(errs...); err != nil {
return trace.AccessDenied(err.Error())
}
return nil
}
type actionConfig struct {
quiet bool
context authz.Context
}
type actionOption func(*actionConfig)
func quietAction(quiet bool) actionOption {
return func(cfg *actionConfig) {
cfg.quiet = quiet
}
}
func (a *ServerWithRoles) withOptions(opts ...actionOption) actionConfig {
cfg := actionConfig{context: a.context}
for _, opt := range opts {
opt(&cfg)
}
return cfg
}
func (c actionConfig) action(namespace, resource string, verbs ...string) error {
if len(verbs) == 0 {
return trace.BadParameter("no verbs provided for authorization check on resource %q", resource)
}
var errs []error
for _, verb := range verbs {
errs = append(errs, c.context.Checker.CheckAccessToRule(&services.Context{User: c.context.User}, namespace, resource, verb, c.quiet))
}
// Convert generic aggregate error to AccessDenied.
if err := trace.NewAggregate(errs...); err != nil {
return trace.AccessDenied(err.Error())
}
return nil
}
func (a *ServerWithRoles) action(namespace, resource string, verbs ...string) error {
return a.withOptions().action(namespace, resource, verbs...)
}
// currentUserAction is a special checker that allows certain actions for users
// even if they are not admins, e.g. update their own passwords,
// or generate certificates, otherwise it will require admin privileges
func (a *ServerWithRoles) currentUserAction(username string) error {
if hasLocalUserRole(a.context) && username == a.context.User.GetName() {
return nil
}
return a.context.Checker.CheckAccessToRule(&services.Context{User: a.context.User},
apidefaults.Namespace, types.KindUser, types.VerbCreate, true)
}
// authConnectorAction is a special checker that grants access to auth
// connectors. It first checks if you have access to the specific connector.
// If not, it checks if the requester has the meta KindAuthConnector access
// (which grants access to all connectors).
func (a *ServerWithRoles) authConnectorAction(namespace string, resource string, verb string) error {
if err := a.context.Checker.CheckAccessToRule(&services.Context{User: a.context.User}, namespace, resource, verb, true); err != nil {
if err := a.context.Checker.CheckAccessToRule(&services.Context{User: a.context.User}, namespace, types.KindAuthConnector, verb, false); err != nil {
return trace.Wrap(err)
}
}
return nil
}
// actionForListWithCondition extracts a restrictive filter condition to be
// added to a list query after a simple resource check fails.
func (a *ServerWithRoles) actionForListWithCondition(namespace, resource, identifier string) (*types.WhereExpr, error) {
origErr := a.withOptions(quietAction(true)).action(namespace, resource, types.VerbList)
if origErr == nil || !trace.IsAccessDenied(origErr) {
return nil, trace.Wrap(origErr)
}
cond, err := a.context.Checker.ExtractConditionForIdentifier(&services.Context{User: a.context.User}, namespace, resource, types.VerbList, identifier)
if trace.IsAccessDenied(err) {
log.WithError(err).Infof("Access to %v %v in namespace %v denied to %v.", types.VerbList, resource, namespace, a.context.Checker)
// Return the original AccessDenied to avoid leaking information.
return nil, trace.Wrap(origErr)
}
return cond, trace.Wrap(err)
}
// actionWithExtendedContext performs an additional RBAC check with extended
// rule context after a simple resource check fails.
func (a *ServerWithRoles) actionWithExtendedContext(namespace, kind, verb string, extendContext func(*services.Context) error) error {
ruleCtx := &services.Context{User: a.context.User}
origErr := a.context.Checker.CheckAccessToRule(ruleCtx, namespace, kind, verb, true)
if origErr == nil || !trace.IsAccessDenied(origErr) {
return trace.Wrap(origErr)
}
if err := extendContext(ruleCtx); err != nil {
log.WithError(err).Warning("Failed to extend context for second RBAC check.")
// Return the original AccessDenied to avoid leaking information.
return trace.Wrap(origErr)
}
return trace.Wrap(a.context.Checker.CheckAccessToRule(ruleCtx, namespace, kind, verb, false))
}
// actionForKindSession is a special checker that grants access to session
// recordings. It can allow access to a specific recording based on the
// `where` section of the user's access rule for kind `session`.
func (a *ServerWithRoles) actionForKindSession(namespace string, sid session.ID) error {
extendContext := func(ctx *services.Context) error {
sessionEnd, err := a.findSessionEndEvent(namespace, sid)
ctx.Session = sessionEnd
return trace.Wrap(err)
}
return trace.Wrap(a.actionWithExtendedContext(namespace, types.KindSession, types.VerbRead, extendContext))
}
// serverAction returns an access denied error if the role is not one of the builtin server roles.
func (a *ServerWithRoles) serverAction() error {
role, ok := a.context.Identity.(authz.BuiltinRole)
if !ok || !role.IsServer() {
return trace.AccessDenied("this request can be only executed by a teleport built-in server")
}
return nil
}
// hasBuiltinRole checks that the attached identity is a builtin role and
// whether any of the given roles match the role set.
func (a *ServerWithRoles) hasBuiltinRole(roles ...types.SystemRole) bool {
for _, role := range roles {
if HasBuiltinRole(a.context, string(role)) {
return true
}
}
return false
}
// HasBuiltinRole checks if the identity is a builtin role with the matching
// name.
func HasBuiltinRole(authContext authz.Context, name string) bool {
if _, ok := authContext.Identity.(authz.BuiltinRole); !ok {
return false
}
if !authContext.Checker.HasRole(name) {
return false
}
return true
}
// HasRemoteBuiltinRole checks if the identity is a remote builtin role with the
// matching name.
func HasRemoteBuiltinRole(authContext authz.Context, name string) bool {
if _, ok := authContext.UnmappedIdentity.(authz.RemoteBuiltinRole); !ok {
return false
}
if !authContext.Checker.HasRole(name) {
return false
}
return true
}
// hasRemoteBuiltinRole checks if the identity is a remote builtin role and the
// name matches.
func (a *ServerWithRoles) hasRemoteBuiltinRole(name string) bool {
return HasRemoteBuiltinRole(a.context, name)
}
// hasRemoteUserRole checks if the identity is a remote user or not.
func hasRemoteUserRole(authContext authz.Context) bool {
_, ok := authContext.UnmappedIdentity.(authz.RemoteUser)
return ok
}
// hasLocalUserRole checks if the identity is a local user or not.
func hasLocalUserRole(authContext authz.Context) bool {
_, ok := authContext.UnmappedIdentity.(authz.LocalUser)
return ok
}
// DevicesClient allows ServerWithRoles to implement ClientI.
// It should not be called through ServerWithRoles,
// as it returns a dummy client that will always respond with "not implemented".
func (a *ServerWithRoles) DevicesClient() devicepb.DeviceTrustServiceClient {
return devicepb.NewDeviceTrustServiceClient(
utils.NewGRPCDummyClientConnection("DevicesClient() should not be called on ServerWithRoles"),
)
}
// LoginRuleClient allows ServerWithRoles to implement ClientI.
// It should not be called through ServerWithRoles,
// as it returns a dummy client that will always respond with "not implemented".
func (a *ServerWithRoles) LoginRuleClient() loginrulepb.LoginRuleServiceClient {
return loginrulepb.NewLoginRuleServiceClient(
utils.NewGRPCDummyClientConnection("LoginRuleClient() should not be called on ServerWithRoles"),
)
}
// OktaClient allows ServerWithRoles to implement ClientI.
// It should not be called through ServerWithRoles,
// as it returns a dummy client that will always respond with "not implemented".
func (a *ServerWithRoles) OktaClient() *okta.Client {
return okta.NewClient(oktapb.NewOktaServiceClient(
utils.NewGRPCDummyClientConnection("OktaClient() should not be called on ServerWithRoles")))
}
// PluginsClient allows ServerWithRoles to implement ClientI.
// It should not be called through ServerWithRoles,
// as it returns a dummy client that will always respond with "not implemented".
func (a *ServerWithRoles) PluginsClient() pluginspb.PluginServiceClient {
return pluginspb.NewPluginServiceClient(
utils.NewGRPCDummyClientConnection("PluginsClient() should not be called on ServerWithRoles"),
)
}
// SAMLIdPClient allows ServerWithRoles to implement ClientI.
// It should not be called through ServerWithRoles,
// as it returns a dummy client that will always respond with "not implemented".
func (a *ServerWithRoles) SAMLIdPClient() samlidppb.SAMLIdPServiceClient {
return samlidppb.NewSAMLIdPServiceClient(
utils.NewGRPCDummyClientConnection("SAMLIdPClient() should not be called on ServerWithRoles"),
)
}
// CreateSessionTracker creates a tracker resource for an active session.
func (a *ServerWithRoles) CreateSessionTracker(ctx context.Context, tracker types.SessionTracker) (types.SessionTracker, error) {
if err := a.serverAction(); err != nil {
return nil, trace.Wrap(err)
}
tracker, err := a.authServer.CreateSessionTracker(ctx, tracker)
if err != nil {
return nil, trace.Wrap(err)
}
return tracker, nil
}
func (a *ServerWithRoles) filterSessionTracker(ctx context.Context, joinerRoles []types.Role, tracker types.SessionTracker, verb string) bool {
// Apply RFD 45 RBAC rules to the session if it's SSH.
// This is a bit of a hack. It converts to the old legacy format
// which we don't have all data for, luckily the fields we don't have aren't made available
// to the RBAC filter anyway.
if tracker.GetKind() == types.KindSSHSession {
ruleCtx := &services.Context{User: a.context.User}
ruleCtx.SSHSession = &session.Session{
ID: session.ID(tracker.GetSessionID()),
Namespace: apidefaults.Namespace,
Login: tracker.GetLogin(),
Created: tracker.GetCreated(),
LastActive: a.authServer.GetClock().Now(),
ServerID: tracker.GetAddress(),
ServerAddr: tracker.GetAddress(),
ServerHostname: tracker.GetHostname(),
ClusterName: tracker.GetClusterName(),
}
for _, participant := range tracker.GetParticipants() {
// We only need to fill in User here since other fields get discarded anyway.
ruleCtx.SSHSession.Parties = append(ruleCtx.SSHSession.Parties, session.Party{
User: participant.User,
})
}
// Skip past it if there's a deny rule in place blocking access.
if err := a.context.Checker.CheckAccessToRule(ruleCtx, apidefaults.Namespace, types.KindSSHSession, verb, true /* silent */); err != nil {
return false
}
}
ruleCtx := &services.Context{User: a.context.User, SessionTracker: tracker}
if a.context.Checker.CheckAccessToRule(ruleCtx, apidefaults.Namespace, types.KindSessionTracker, types.VerbList, true /* silent */) == nil {
return true
}
evaluator := NewSessionAccessEvaluator(tracker.GetHostPolicySets(), tracker.GetSessionKind(), tracker.GetHostUser())
modes := evaluator.CanJoin(SessionAccessContext{Username: a.context.User.GetName(), Roles: joinerRoles})
return len(modes) != 0
}
const (
forwardedTag = "teleport.forwarded.for"
)
// 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.
//
// All spans received will have a `teleport.forwarded.for` attribute added to them with the value being one of
// two things depending on the role of the forwarder:
// 1. User forwarded: `teleport.forwarded.for: alice`
// 2. Instance forwarded: `teleport.forwarded.for: Proxy.clustername:Proxy,Node,Instance`
//
// This allows upstream consumers of the spans to be able to identify forwarded spans and act on them accordingly.
func (a *ServerWithRoles) Export(ctx context.Context, req *collectortracev1.ExportTraceServiceRequest) (*collectortracev1.ExportTraceServiceResponse, error) {
var sb strings.Builder
sb.WriteString(a.context.User.GetName())
// if forwarded on behalf of a Teleport service add its system roles
if role, ok := a.context.Identity.(authz.BuiltinRole); ok {
sb.WriteRune(':')
sb.WriteString(role.Role.String())
if len(role.AdditionalSystemRoles) > 0 {
sb.WriteRune(',')
sb.WriteString(role.AdditionalSystemRoles.String())
}
}
// the forwarded attribute to add
value := &otlpcommonv1.KeyValue{
Key: forwardedTag,
Value: &otlpcommonv1.AnyValue{
Value: &otlpcommonv1.AnyValue_StringValue{
StringValue: sb.String(),
},
},
}
// returns the index at which the attribute with
// the forwardedTag key exists, -1 if not found
tagIndex := func(attrs []*otlpcommonv1.KeyValue) int {
for i, attr := range attrs {
if attr.Key == forwardedTag {
return i
}
}
return -1
}
for _, resourceSpans := range req.ResourceSpans {
// if there is a resource, tag it with the
// forwarded attribute instead of each of tagging
// each span
if resourceSpans.Resource != nil {
if index := tagIndex(resourceSpans.Resource.Attributes); index != -1 {
resourceSpans.Resource.Attributes[index] = value
} else {
resourceSpans.Resource.Attributes = append(resourceSpans.Resource.Attributes, value)
}
// override any span attributes with a forwarded tag,
// but we don't need to add one if the span isn't already
// tagged since we just tagged the resource
for _, scopeSpans := range resourceSpans.ScopeSpans {
for _, span := range scopeSpans.Spans {
if index := tagIndex(span.Attributes); index != -1 {
span.Attributes[index] = value
}
}
}
continue
}
// there was no resource, so we must now tag all the
// individual spans with the forwarded tag
for _, scopeSpans := range resourceSpans.ScopeSpans {
for _, span := range scopeSpans.Spans {
if index := tagIndex(span.Attributes); index != -1 {
span.Attributes[index] = value
} else {
span.Attributes = append(span.Attributes, value)
}
}
}
}
if err := a.authServer.traceClient.UploadTraces(ctx, req.ResourceSpans); err != nil {
return &collectortracev1.ExportTraceServiceResponse{}, trace.Wrap(err)
}
return &collectortracev1.ExportTraceServiceResponse{}, nil
}
// GetSessionTracker returns the current state of a session tracker for an active session.
func (a *ServerWithRoles) GetSessionTracker(ctx context.Context, sessionID string) (types.SessionTracker, error) {
tracker, err := a.authServer.GetSessionTracker(ctx, sessionID)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.serverAction(); err == nil {
return tracker, nil
}
user := a.context.User
joinerRoles, err := services.FetchRoles(user.GetRoles(), a.authServer, user.GetTraits())
if err != nil {
return nil, trace.Wrap(err)
}
ok := a.filterSessionTracker(ctx, joinerRoles, tracker, types.VerbRead)
if !ok {
return nil, trace.NotFound("session %v not found", sessionID)
}
return tracker, nil
}
// GetActiveSessionTrackers returns a list of active session trackers.
func (a *ServerWithRoles) GetActiveSessionTrackers(ctx context.Context) ([]types.SessionTracker, error) {
sessions, err := a.authServer.GetActiveSessionTrackers(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.serverAction(); err == nil {
return sessions, nil
}
var filteredSessions []types.SessionTracker
user := a.context.User
joinerRoles, err := services.FetchRoles(user.GetRoles(), a.authServer, user.GetTraits())
if err != nil {
return nil, trace.Wrap(err)
}
for _, sess := range sessions {
ok := a.filterSessionTracker(ctx, joinerRoles, sess, types.VerbList)
if ok {
filteredSessions = append(filteredSessions, sess)
}
}
return filteredSessions, nil
}
// GetActiveSessionTrackersWithFilter returns a list of active sessions filtered by a filter.
func (a *ServerWithRoles) GetActiveSessionTrackersWithFilter(ctx context.Context, filter *types.SessionTrackerFilter) ([]types.SessionTracker, error) {
sessions, err := a.authServer.GetActiveSessionTrackersWithFilter(ctx, filter)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.serverAction(); err == nil {
return sessions, nil
}
var filteredSessions []types.SessionTracker
user := a.context.User
joinerRoles, err := services.FetchRoles(user.GetRoles(), a.authServer, user.GetTraits())
if err != nil {
return nil, trace.Wrap(err)
}
for _, sess := range sessions {
ok := a.filterSessionTracker(ctx, joinerRoles, sess, types.VerbList)
if ok {
filteredSessions = append(filteredSessions, sess)
}
}
return filteredSessions, nil
}
// RemoveSessionTracker removes a tracker resource for an active session.
func (a *ServerWithRoles) RemoveSessionTracker(ctx context.Context, sessionID string) error {
if err := a.serverAction(); err != nil {
return trace.Wrap(err)
}
return a.authServer.RemoveSessionTracker(ctx, sessionID)
}
// UpdateSessionTracker updates a tracker resource for an active session.
func (a *ServerWithRoles) UpdateSessionTracker(ctx context.Context, req *proto.UpdateSessionTrackerRequest) error {
if err := a.serverAction(); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpdateSessionTracker(ctx, req)
}
// AuthenticateWebUser authenticates web user, creates and returns a web session
// in case authentication is successful
func (a *ServerWithRoles) AuthenticateWebUser(ctx context.Context, req AuthenticateUserRequest) (types.WebSession, error) {
// authentication request has it's own authentication, however this limits the requests
// types to proxies to make it harder to break
if !a.hasBuiltinRole(types.RoleProxy) {
return nil, trace.AccessDenied("this request can be only executed by a proxy")
}
return a.authServer.AuthenticateWebUser(ctx, req)
}
// AuthenticateSSHUser authenticates SSH console user, creates and returns a pair of signed TLS and SSH
// short lived certificates as a result
func (a *ServerWithRoles) AuthenticateSSHUser(ctx context.Context, req AuthenticateSSHRequest) (*SSHLoginResponse, error) {
// authentication request has it's own authentication, however this limits the requests
// types to proxies to make it harder to break
if !a.hasBuiltinRole(types.RoleProxy) {
return nil, trace.AccessDenied("this request can be only executed by a proxy")
}
return a.authServer.AuthenticateSSHUser(ctx, req)
}
// GenerateOpenSSHCert signs a SSH certificate that can be used
// to connect to Agentless nodes.
func (a *ServerWithRoles) GenerateOpenSSHCert(ctx context.Context, req *proto.OpenSSHCertRequest) (*proto.OpenSSHCert, error) {
// this limits the requests types to proxies to make it harder to break
if !a.hasBuiltinRole(types.RoleProxy) {
return nil, trace.AccessDenied("this request can be only executed by a proxy")
}
return a.authServer.GenerateOpenSSHCert(ctx, req)
}
// CreateCertAuthority not implemented: can only be called locally.
func (a *ServerWithRoles) CreateCertAuthority(ctx context.Context, ca types.CertAuthority) error {
return trace.NotImplemented(notImplementedMessage)
}
// RotateCertAuthority starts or restarts certificate authority rotation process.
func (a *ServerWithRoles) RotateCertAuthority(ctx context.Context, req RotateRequest) error {
if err := req.CheckAndSetDefaults(a.authServer.clock); err != nil {
return trace.Wrap(err)
}
if err := a.action(apidefaults.Namespace, types.KindCertAuthority, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.RotateCertAuthority(ctx, req)
}
// RotateExternalCertAuthority rotates external certificate authority,
// this method is called by a remote trusted cluster and is used to update
// only public keys and certificates of the certificate authority.
func (a *ServerWithRoles) RotateExternalCertAuthority(ctx context.Context, ca types.CertAuthority) error {
if ca == nil {
return trace.BadParameter("missing certificate authority")
}
sctx := &services.Context{User: a.context.User, Resource: ca}
if err := a.actionWithContext(sctx, apidefaults.Namespace, types.KindCertAuthority, types.VerbRotate); err != nil {
return trace.Wrap(err)
}
return a.authServer.RotateExternalCertAuthority(ctx, ca)
}
// UpsertCertAuthority updates existing cert authority or updates the existing one.
func (a *ServerWithRoles) UpsertCertAuthority(ctx context.Context, ca types.CertAuthority) error {
trust, err := trustv1.NewService(&trustv1.ServiceConfig{
Authorizer: authz.AuthorizerFunc(func(context.Context) (*authz.Context, error) {
return &a.context, nil
}),
Cache: a.authServer.Cache,
Backend: a.authServer.Services,
})
if err != nil {
return trace.Wrap(err)
}
cav2, ok := ca.(*types.CertAuthorityV2)
if !ok {
return trace.BadParameter("unexpected ca type %T", ca)
}
_, err = trust.UpsertCertAuthority(ctx, &trustpb.UpsertCertAuthorityRequest{CertAuthority: cav2})
return trace.Wrap(err)
}
// CompareAndSwapCertAuthority updates existing cert authority if the existing cert authority
// value matches the value stored in the backend.
func (a *ServerWithRoles) CompareAndSwapCertAuthority(new, existing types.CertAuthority) error {
if err := a.action(apidefaults.Namespace, types.KindCertAuthority, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.CompareAndSwapCertAuthority(new, existing)
}
func (a *ServerWithRoles) GetCertAuthorities(ctx context.Context, caType types.CertAuthType, loadKeys bool) ([]types.CertAuthority, error) {
trust, err := trustv1.NewService(&trustv1.ServiceConfig{
Authorizer: authz.AuthorizerFunc(func(context.Context) (*authz.Context, error) {
return &a.context, nil
}),
Cache: a.authServer.Cache,
Backend: a.authServer.Services,
})
if err != nil {
return nil, trace.Wrap(err)
}
resp, err := trust.GetCertAuthorities(ctx, &trustpb.GetCertAuthoritiesRequest{Type: string(caType), IncludeKey: loadKeys})
if err != nil {
return nil, trace.Wrap(err)
}
cas := make([]types.CertAuthority, 0, len(resp.CertAuthoritiesV2))
for _, ca := range resp.CertAuthoritiesV2 {
cas = append(cas, ca)
}
return cas, trace.Wrap(err)
}
func (a *ServerWithRoles) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool) (types.CertAuthority, error) {
trust, err := trustv1.NewService(&trustv1.ServiceConfig{
Authorizer: authz.AuthorizerFunc(func(context.Context) (*authz.Context, error) {
return &a.context, nil
}),
Cache: a.authServer.Cache,
Backend: a.authServer.Services,
})
if err != nil {
return nil, trace.Wrap(err)
}
resp, err := trust.GetCertAuthority(ctx, &trustpb.GetCertAuthorityRequest{
Domain: id.DomainName,
Type: string(id.Type),
IncludeKey: loadKeys,
})
if err != nil {
return nil, trace.Wrap(err)
}
return resp, trace.Wrap(err)
}
func (a *ServerWithRoles) GetDomainName(ctx context.Context) (string, error) {
// anyone can read it, no harm in that
return a.authServer.GetDomainName()
}
// 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 concatenated.
func (a *ServerWithRoles) GetClusterCACert(
ctx context.Context,
) (*proto.GetClusterCACertResponse, error) {
// Allow all roles to get the CA certs.
return a.authServer.GetClusterCACert(ctx)
}
func (a *ServerWithRoles) DeleteCertAuthority(ctx context.Context, id types.CertAuthID) error {
trust, err := trustv1.NewService(&trustv1.ServiceConfig{
Authorizer: authz.AuthorizerFunc(func(context.Context) (*authz.Context, error) {
return &a.context, nil
}),
Cache: a.authServer.Cache,
Backend: a.authServer.Services,
})
if err != nil {
return trace.Wrap(err)
}
if _, err := trust.DeleteCertAuthority(ctx, &trustpb.DeleteCertAuthorityRequest{Domain: id.DomainName, Type: string(id.Type)}); err != nil {
return trace.Wrap(err)
}
return nil
}
// ActivateCertAuthority not implemented: can only be called locally.
func (a *ServerWithRoles) ActivateCertAuthority(id types.CertAuthID) error {
return trace.NotImplemented(notImplementedMessage)
}
// DeactivateCertAuthority not implemented: can only be called locally.
func (a *ServerWithRoles) DeactivateCertAuthority(id types.CertAuthID) error {
return trace.NotImplemented(notImplementedMessage)
}
// UpdateUserCARoleMap not implemented: can only be called locally.
func (a *ServerWithRoles) UpdateUserCARoleMap(ctx context.Context, name string, roleMap types.RoleMap, activated bool) error {
return trace.NotImplemented(notImplementedMessage)
}
// GenerateToken generates multi-purpose authentication token.
// Deprecated: Use CreateToken or UpdateToken.
// DELETE IN 14.0.0, replaced by methods above (strideynet).
func (a *ServerWithRoles) GenerateToken(ctx context.Context, req *proto.GenerateTokenRequest) (string, error) {
if err := a.action(apidefaults.Namespace, types.KindToken, types.VerbCreate); err != nil {
return "", trace.Wrap(err)
}
return a.authServer.GenerateToken(ctx, req)
}
func (a *ServerWithRoles) RegisterUsingToken(ctx context.Context, req *types.RegisterUsingTokenRequest) (*proto.Certs, error) {
// tokens have authz mechanism on their own, no need to check
return a.authServer.RegisterUsingToken(ctx, req)
}
// RegisterUsingIAMMethod registers the caller using the IAM join method and
// returns signed certs to join the cluster.
//
// See (*Server).RegisterUsingIAMMethod for further documentation.
//
// This wrapper does not do any extra authz checks, as the register method has
// its own authz mechanism.
func (a *ServerWithRoles) RegisterUsingIAMMethod(ctx context.Context, challengeResponse client.RegisterIAMChallengeResponseFunc) (*proto.Certs, error) {
certs, err := a.authServer.RegisterUsingIAMMethod(ctx, challengeResponse)
return certs, trace.Wrap(err)
}
// RegisterUsingAzureMethod registers the caller using the Azure join method and
// returns signed certs to join the cluster.
//
// See (*Server).RegisterUsingAzureMethod for further documentation.
//
// This wrapper does not do any extra authz checks, as the register method has
// its own authz mechanism.
func (a *ServerWithRoles) RegisterUsingAzureMethod(ctx context.Context, challengeResponse client.RegisterAzureChallengeResponseFunc) (*proto.Certs, error) {
certs, err := a.authServer.RegisterUsingAzureMethod(ctx, challengeResponse)
return certs, trace.Wrap(err)
}
// GenerateHostCerts generates new host certificates (signed
// by the host certificate authority) for a node.
func (a *ServerWithRoles) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequest) (*proto.Certs, error) {
clusterName, err := a.authServer.GetDomainName()
if err != nil {
return nil, trace.Wrap(err)
}
// username is hostID + cluster name, so make sure server requests new keys for itself
if a.context.User.GetName() != HostFQDN(req.HostID, clusterName) {
return nil, trace.AccessDenied("username mismatch %q and %q", a.context.User.GetName(), HostFQDN(req.HostID, clusterName))
}
if req.Role == types.RoleInstance {
if err := a.checkAdditionalSystemRoles(ctx, req); err != nil {
return nil, trace.Wrap(err)
}
} else {
if len(req.SystemRoles) != 0 {
return nil, trace.AccessDenied("additional system role encoding not supported for certs of type %q", req.Role)
}
}
existingRoles, err := types.NewTeleportRoles(a.context.User.GetRoles())
if err != nil {
return nil, trace.Wrap(err)
}
// prohibit privilege escalations through role changes (except the instance cert exception, handled above).
if !a.hasBuiltinRole(req.Role) && req.Role != types.RoleInstance {
return nil, trace.AccessDenied("roles do not match: %v and %v", existingRoles, req.Role)
}
return a.authServer.GenerateHostCerts(ctx, req)
}
// checkAdditionalSystemRoles verifies additional system roles in host cert request.
func (a *ServerWithRoles) checkAdditionalSystemRoles(ctx context.Context, req *proto.HostCertsRequest) error {
// ensure requesting cert's primary role is a server role.
role, ok := a.context.Identity.(authz.BuiltinRole)
if !ok || !role.IsServer() {
return trace.AccessDenied("additional system roles can only be claimed by a teleport built-in server")
}
// check that additional system roles are theoretically valid (distinct from permissibility, which
// is checked in the following loop).
for _, r := range req.SystemRoles {
if r.Check() != nil {
return trace.AccessDenied("additional system role %q cannot be applied (not a valid system role)", r)
}
if !r.IsLocalService() {
return trace.AccessDenied("additional system role %q cannot be applied (not a builtin service role)", r)
}
}
// load system role assertions if relevant
var assertions proto.UnstableSystemRoleAssertionSet
var err error
if req.UnstableSystemRoleAssertionID != "" {
assertions, err = a.authServer.UnstableGetSystemRoleAssertions(ctx, req.HostID, req.UnstableSystemRoleAssertionID)
if err != nil {
// include this error in the logs, since it might be indicative of a bug if it occurs outside of the context
// of a general backend outage.
log.Warnf("Failed to load system role assertion set %q for instance %q: %v", req.UnstableSystemRoleAssertionID, req.HostID, err)
return trace.AccessDenied("failed to load system role assertion set with ID %q", req.UnstableSystemRoleAssertionID)
}
}
// check if additional system roles are permissible
Outer:
for _, requestedRole := range req.SystemRoles {
if a.hasBuiltinRole(requestedRole) {
// instance is already known to hold this role
continue Outer
}
for _, assertedRole := range assertions.SystemRoles {
if requestedRole == assertedRole {
// instance recently demonstrated that it holds this role
continue Outer
}
}
return trace.AccessDenied("additional system role %q cannot be applied (not authorized)", requestedRole)
}
return nil
}
func (a *ServerWithRoles) UnstableAssertSystemRole(ctx context.Context, req proto.UnstableSystemRoleAssertion) error {
role, ok := a.context.Identity.(authz.BuiltinRole)
if !ok || !role.IsServer() {
return trace.AccessDenied("system role assertions can only be executed by a teleport built-in server")
}
if req.ServerID != role.GetServerID() {
return trace.AccessDenied("system role assertions do not support impersonation (%q -> %q)", role.GetServerID(), req.ServerID)
}
if !a.hasBuiltinRole(req.SystemRole) {
return trace.AccessDenied("cannot assert unheld system role %q", req.SystemRole)
}
if !req.SystemRole.IsLocalService() {
return trace.AccessDenied("cannot assert non-service system role %q", req.SystemRole)
}
return a.authServer.UnstableAssertSystemRole(ctx, req)
}
// RegisterInventoryControlStream handles the upstream half of the control stream handshake, then passes the control stream to
// the auth server's main control logic. We also return the post-auth hello message back up to the grpcserver layer in order to
// use it for metrics purposes.
func (a *ServerWithRoles) RegisterInventoryControlStream(ics client.UpstreamInventoryControlStream) (proto.UpstreamInventoryHello, error) {
// this value gets set further down
var hello proto.UpstreamInventoryHello
// Ensure that caller is a teleport server
role, ok := a.context.Identity.(authz.BuiltinRole)
if !ok || !role.IsServer() {
return hello, trace.AccessDenied("inventory control streams can only be created by a teleport built-in server")
}
// wait for upstream hello
select {
case msg := <-ics.Recv():
switch m := msg.(type) {
case proto.UpstreamInventoryHello:
hello = m
default:
return hello, trace.BadParameter("expected upstream hello, got: %T", m)
}
case <-ics.Done():
return hello, trace.Wrap(ics.Error())
case <-a.CloseContext().Done():
return hello, trace.Errorf("auth server shutdown")
}
// verify that server is creating stream on behalf of itself.
if hello.ServerID != role.GetServerID() {
return hello, trace.AccessDenied("control streams do not support impersonation (%q -> %q)", role.GetServerID(), hello.ServerID)
}
// in order to reduce sensitivity to downgrades/misconfigurations, we simply filter out
// services that are unrecognized or unauthorized, rather than rejecting hellos that claim them.
var filteredServices []types.SystemRole
for _, service := range hello.Services {
if !a.hasBuiltinRole(service) {
log.Warnf("Omitting service %q for control stream of instance %q (unknown or unauthorized).", service, role.GetServerID())
continue
}
filteredServices = append(filteredServices, service)
}
hello.Services = filteredServices
return hello, a.authServer.RegisterInventoryControlStream(ics, hello)
}
func (a *ServerWithRoles) GetInventoryStatus(ctx context.Context, req proto.InventoryStatusRequest) (proto.InventoryStatusSummary, error) {
// only support builtin roles for now, but we'll eventually want to develop an RBAC syntax for
// the inventory APIs once they are more developed.
if !a.hasBuiltinRole(types.RoleAdmin, types.RoleProxy) {
return proto.InventoryStatusSummary{}, trace.AccessDenied("requires builtin admin role")
}
return a.authServer.GetInventoryStatus(ctx, req), nil
}
func (a *ServerWithRoles) PingInventory(ctx context.Context, req proto.InventoryPingRequest) (proto.InventoryPingResponse, error) {
// admin-only for now, but we'll eventually want to develop an RBAC syntax for
// the inventory APIs once they are more developed.
if !a.hasBuiltinRole(types.RoleAdmin) {
return proto.InventoryPingResponse{}, trace.AccessDenied("requires builtin admin role")
}
return a.authServer.PingInventory(ctx, req)
}
func (a *ServerWithRoles) GetInstances(ctx context.Context, filter types.InstanceFilter) stream.Stream[types.Instance] {
if err := a.action(apidefaults.Namespace, types.KindInstance, types.VerbList, types.VerbRead); err != nil {
return stream.Fail[types.Instance](trace.Wrap(err))
}
return a.authServer.GetInstances(ctx, filter)
}
func (a *ServerWithRoles) GetClusterAlerts(ctx context.Context, query types.GetClusterAlertsRequest) ([]types.ClusterAlert, error) {
// unauthenticated clients can never check for alerts. we don't normally explicitly
// check for this kind of thing, but since alerts use an unusual access-control
// pattern, explicitly rejecting the nop role makes things easier.
if a.hasBuiltinRole(types.RoleNop) {
return nil, trace.AccessDenied("alerts not available to unauthenticated clients")
}
alerts, err := a.authServer.GetClusterAlerts(ctx, query)
if err != nil {
return nil, trace.Wrap(err)
}
var acks []types.AlertAcknowledgement
if !query.WithAcknowledged {
// load acks so that we can filter out acknowledged alerts
acks, err = a.authServer.GetAlertAcks(ctx)
if err != nil {
// we don't fail here since users are allowed to see acknowledged alerts, acks
// are intended only as a tool for reducing noise.
log.Warnf("Failed to load alert acks: %v", err)
}
}
// admin skips rbac checks, but still obeys acks and supersessions, so
// we store the result of the check for use per-alert during filtering.
isAdmin := a.hasBuiltinRole(types.RoleAdmin)
// filter alerts by acks and teleport.internal 'permit' labels to determine whether the alert
// was intended to be visible to the calling user.
filtered := alerts[:0]
Outer:
for _, alert := range alerts {
// skip acknowledged alerts
for _, ack := range acks {
if ack.AlertID == alert.Metadata.Name {
continue Outer
}
}
// remaining checks in this loop are access-controls, so short-circuit
// if caller is admin.
if isAdmin {
filtered = append(filtered, alert)
continue Outer
}
if alert.Metadata.Labels[types.AlertPermitAll] == "yes" {
// alert may be shown to all authenticated users
filtered = append(filtered, alert)
continue Outer
}
// the verb-permit label permits users to view an alert if they hold
// one of the specified <resource>:<verb> pairs (e.g. `node:list|token:create`
// would be satisfied by either a user that can list nodes *or* create tokens).
Verbs:
for _, s := range strings.Split(alert.Metadata.Labels[types.AlertVerbPermit], "|") {
rv := strings.Split(s, ":")
if len(rv) != 2 {
continue Verbs
}
if a.withOptions(quietAction(true)).action(apidefaults.Namespace, rv[0], rv[1]) == nil {
// user holds at least one of the resource:verb pairs specified by
// the verb-permit label.
filtered = append(filtered, alert)
continue Outer
}
}
}
alerts = filtered
if !query.WithSuperseded {
// aggregate supersede directives and filter. we do this as a separate filter
// step since we only obey supersede relationships within the set of
// visible alerts (i.e. an alert that isn't visible cannot supersede an alert
// that is visible).
sups := make(map[string]types.AlertSeverity)
for _, alert := range alerts {
for _, id := range strings.Split(alert.Metadata.Labels[types.AlertSupersedes], ",") {
if sups[id] < alert.Spec.Severity {
sups[id] = alert.Spec.Severity
}
}
}
filtered = alerts[:0]
for _, alert := range alerts {
if sups[alert.Metadata.Name] > alert.Spec.Severity {
continue
}
filtered = append(filtered, alert)
}
alerts = filtered
}
return alerts, nil
}
func (a *ServerWithRoles) UpsertClusterAlert(ctx context.Context, alert types.ClusterAlert) error {
// admin-only API. the expected usage of this is mostly as something the auth server itself would do
// internally, but it is useful to be able to create alerts via tctl for testing/debug purposes.
if !a.hasBuiltinRole(types.RoleAdmin) {
return trace.AccessDenied("cluster alert creation is admin-only")
}
return a.authServer.UpsertClusterAlert(ctx, alert)
}
func (a *ServerWithRoles) CreateAlertAck(ctx context.Context, ack types.AlertAcknowledgement) error {
// alert acknowledgement is admin-only for now as it is a fairly niche feature,
// but we may want to develop custom rbac for this feature in the future
// if use of cluster alerts becomes more widespread.
if !a.hasBuiltinRole(types.RoleAdmin) {
return trace.AccessDenied("alert ack is admin-only")
}
return a.authServer.CreateAlertAck(ctx, ack)
}
func (a *ServerWithRoles) GetAlertAcks(ctx context.Context) ([]types.AlertAcknowledgement, error) {
// alert acknowledgement is admin-only for now as it is a fairly niche feature,
// but we may want to develop custom rbac for this feature in the future
// if use of cluster alerts becomes more widespread.
if !a.hasBuiltinRole(types.RoleAdmin) {
return nil, trace.AccessDenied("listing alert acks is admin-only")
}
return a.authServer.GetAlertAcks(ctx)
}
func (a *ServerWithRoles) ClearAlertAcks(ctx context.Context, req proto.ClearAlertAcksRequest) error {
// alert acknowledgement is admin-only for now as it is a fairly niche feature,
// but we may want to develop custom rbac for this feature in the future
// if use of cluster alerts becomes more widespread.
if !a.hasBuiltinRole(types.RoleAdmin) {
return trace.AccessDenied("clearing alert acks is admin-only")
}
return a.authServer.ClearAlertAcks(ctx, req)
}
func (a *ServerWithRoles) UpsertNode(ctx context.Context, s types.Server) (*types.KeepAlive, error) {
if err := a.action(s.GetNamespace(), types.KindNode, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.UpsertNode(ctx, s)
}
// DELETE IN: 5.1.0
//
// This logic has moved to KeepAliveServer.
func (a *ServerWithRoles) KeepAliveNode(ctx context.Context, handle types.KeepAlive) error {
if !a.hasBuiltinRole(types.RoleNode) {
return trace.AccessDenied("[10] access denied")
}
clusterName, err := a.GetDomainName(ctx)
if err != nil {
return trace.Wrap(err)
}
serverName, err := ExtractHostID(a.context.User.GetName(), clusterName)
if err != nil {
return trace.AccessDenied("[10] access denied")
}
if serverName != handle.Name {
return trace.AccessDenied("[10] access denied")
}
if err := a.action(apidefaults.Namespace, types.KindNode, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.KeepAliveNode(ctx, handle)
}
// KeepAliveServer updates expiry time of a server resource.
func (a *ServerWithRoles) KeepAliveServer(ctx context.Context, handle types.KeepAlive) error {
clusterName, err := a.GetDomainName(ctx)
if err != nil {
return trace.Wrap(err)
}
serverName, err := ExtractHostID(a.context.User.GetName(), clusterName)
if err != nil {
return trace.AccessDenied("access denied")
}
switch handle.GetType() {
case constants.KeepAliveNode:
if serverName != handle.Name {
return trace.AccessDenied("access denied")
}
if !a.hasBuiltinRole(types.RoleNode) {
return trace.AccessDenied("access denied")
}
if err := a.action(apidefaults.Namespace, types.KindNode, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
case constants.KeepAliveApp:
if handle.HostID != "" {
if serverName != handle.HostID {
return trace.AccessDenied("access denied")
}
} else { // DELETE IN 9.0. Legacy app server is heartbeating back.
if serverName != handle.Name {
return trace.AccessDenied("access denied")
}
}
if !a.hasBuiltinRole(types.RoleApp) {
return trace.AccessDenied("access denied")
}
if err := a.action(apidefaults.Namespace, types.KindAppServer, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
case constants.KeepAliveDatabase:
// There can be multiple database servers per host so they send their
// host ID in a separate field because unlike SSH nodes the resource
// name cannot be the host ID.
if serverName != handle.HostID {
return trace.AccessDenied("access denied")
}
if !a.hasBuiltinRole(types.RoleDatabase) {
return trace.AccessDenied("access denied")
}
if err := a.action(apidefaults.Namespace, types.KindDatabaseServer, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
case constants.KeepAliveWindowsDesktopService:
if serverName != handle.Name {
return trace.AccessDenied("access denied")
}
if !a.hasBuiltinRole(types.RoleWindowsDesktop) {
return trace.AccessDenied("access denied")
}
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktopService, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
case constants.KeepAliveKube:
if handle.HostID == "" {
return trace.BadParameter("hostID is required for kubernetes keep alive")
} else if serverName != handle.HostID {
return trace.AccessDenied("access denied")
}
if !a.hasBuiltinRole(types.RoleKube) {
return trace.AccessDenied("access denied")
}
if err := a.action(apidefaults.Namespace, types.KindKubeServer, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
case constants.KeepAliveDatabaseService:
if serverName != handle.Name {
return trace.AccessDenied("access denied")
}
if !a.hasBuiltinRole(types.RoleDatabase) {
return trace.AccessDenied("access denied")
}
if err := a.action(apidefaults.Namespace, types.KindDatabaseService, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
default:
return trace.BadParameter("unknown keep alive type %q", handle.Type)
}
return a.authServer.KeepAliveServer(ctx, handle)
}
// NewWatcher returns a new event watcher
func (a *ServerWithRoles) NewWatcher(ctx context.Context, watch types.Watch) (types.Watcher, error) {
if len(watch.Kinds) == 0 {
return nil, trace.AccessDenied("can't setup global watch")
}
for _, kind := range watch.Kinds {
// Check the permissions for data of each kind. For watching, most
// kinds of data just need a Read permission, but some have more
// complicated logic.
switch kind.Kind {
case types.KindCertAuthority:
verb := types.VerbReadNoSecrets
if kind.LoadSecrets {
verb = types.VerbRead
}
if err := a.action(apidefaults.Namespace, types.KindCertAuthority, verb); err != nil {
return nil, trace.Wrap(err)
}
case types.KindAccessRequest:
var filter types.AccessRequestFilter
if err := filter.FromMap(kind.Filter); err != nil {
return nil, trace.Wrap(err)
}
if filter.User == "" || a.currentUserAction(filter.User) != nil {
if err := a.action(apidefaults.Namespace, types.KindAccessRequest, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
case types.KindAppServer:
if err := a.action(apidefaults.Namespace, types.KindAppServer, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
case types.KindWebSession:
var filter types.WebSessionFilter
if err := filter.FromMap(kind.Filter); err != nil {
return nil, trace.Wrap(err)
}
resource := types.KindWebSession
// Allow reading Snowflake sessions to DB service.
if kind.SubKind == types.KindSnowflakeSession {
resource = types.KindDatabase
}
if filter.User == "" || a.currentUserAction(filter.User) != nil {
if err := a.action(apidefaults.Namespace, resource, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
case types.KindWebToken:
if err := a.action(apidefaults.Namespace, types.KindWebToken, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
case types.KindRemoteCluster:
if err := a.action(apidefaults.Namespace, types.KindRemoteCluster, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
case types.KindDatabaseServer:
if err := a.action(apidefaults.Namespace, types.KindDatabaseServer, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
case types.KindKubeServer:
if err := a.action(apidefaults.Namespace, types.KindKubeServer, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
case types.KindWindowsDesktopService:
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktopService, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
default:
if err := a.action(apidefaults.Namespace, kind.Kind, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
}
switch {
case a.hasBuiltinRole(types.RoleProxy):
watch.QueueSize = defaults.ProxyQueueSize
case a.hasBuiltinRole(types.RoleNode):
watch.QueueSize = defaults.NodeQueueSize
}
return a.authServer.NewWatcher(ctx, watch)
}
// DeleteAllNodes deletes all nodes in a given namespace
func (a *ServerWithRoles) DeleteAllNodes(ctx context.Context, namespace string) error {
if err := a.action(namespace, types.KindNode, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllNodes(ctx, namespace)
}
// DeleteNode deletes node in the namespace
func (a *ServerWithRoles) DeleteNode(ctx context.Context, namespace, node string) error {
if err := a.action(namespace, types.KindNode, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteNode(ctx, namespace, node)
}
// GetNode gets a node by name and namespace.
func (a *ServerWithRoles) GetNode(ctx context.Context, namespace, name string) (types.Server, error) {
if err := a.action(namespace, types.KindNode, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
node, err := a.authServer.GetNode(ctx, namespace, name)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.checkAccessToNode(node); err != nil {
if trace.IsAccessDenied(err) {
return nil, trace.NotFound("not found")
}
return nil, trace.Wrap(err)
}
return node, nil
}
func (a *ServerWithRoles) GetNodes(ctx context.Context, namespace string) ([]types.Server, error) {
if err := a.action(namespace, types.KindNode, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
// Fetch full list of nodes in the backend.
startFetch := time.Now()
nodes, err := a.authServer.GetNodes(ctx, namespace)
if err != nil {
return nil, trace.Wrap(err)
}
elapsedFetch := time.Since(startFetch)
// Filter nodes to return the ones for the connected identity.
filteredNodes := make([]types.Server, 0)
startFilter := time.Now()
for _, node := range nodes {
if err := a.checkAccessToNode(node); err != nil {
if trace.IsAccessDenied(err) {
continue
}
return nil, trace.Wrap(err)
}
filteredNodes = append(filteredNodes, node)
}
elapsedFilter := time.Since(startFilter)
log.WithFields(logrus.Fields{
"user": a.context.User.GetName(),
"elapsed_fetch": elapsedFetch,
"elapsed_filter": elapsedFilter,
}).Debugf(
"GetServers(%v->%v) in %v.",
len(nodes), len(filteredNodes), elapsedFetch+elapsedFilter)
return filteredNodes, nil
}
// ListResources returns a paginated list of resources filtered by user access.
func (a *ServerWithRoles) ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) {
if req.UseSearchAsRoles || req.UsePreviewAsRoles {
var extraRoles []string
if req.UseSearchAsRoles {
extraRoles = append(extraRoles, a.context.Checker.GetAllowedSearchAsRoles()...)
}
if req.UsePreviewAsRoles {
extraRoles = append(extraRoles, a.context.Checker.GetAllowedPreviewAsRoles()...)
}
clusterName, err := a.authServer.GetClusterName()
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.context.UseExtraRoles(a.authServer, clusterName.GetClusterName(), extraRoles); err != nil {
return nil, trace.Wrap(err)
}
a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.AccessRequestResourceSearch{
Metadata: apievents.Metadata{
Type: events.AccessRequestResourceSearch,
Code: events.AccessRequestResourceSearchCode,
},
UserMetadata: authz.ClientUserMetadata(ctx),
SearchAsRoles: a.context.Checker.RoleNames(),
ResourceType: req.ResourceType,
Namespace: req.Namespace,
Labels: req.Labels,
PredicateExpression: req.PredicateExpression,
SearchKeywords: req.SearchKeywords,
})
}
// ListResources request coming through this auth layer gets request filters
// stripped off and saved to be applied later after items go through rbac checks.
// The list that gets returned from the backend comes back unfiltered and as
// we apply request filters, we might make multiple trips to get more subsets to
// reach our limit, which is fine b/c we can start query with our next key.
//
// But since sorting and counting totals requires us to work with entire list upfront,
// special handling is needed in this layer b/c if we try to mimic the "subset" here,
// we will be making unnecessary trips and doing needless work of deserializing every
// item for every subset.
if req.RequiresFakePagination() {
resp, err := a.listResourcesWithSort(ctx, req)
if err != nil {
return nil, trace.Wrap(err)
}
return resp, nil
}
// Start real pagination.
if err := req.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
limit := int(req.Limit)
actionVerbs := []string{types.VerbList, types.VerbRead}
switch req.ResourceType {
case types.KindNode:
// We are checking list only for Nodes to keep backwards compatibility.
// The read verb got added to GetNodes initially in:
// https://github.com/gravitational/teleport/pull/1209
// but got removed shortly afterwards in:
// https://github.com/gravitational/teleport/pull/1224
actionVerbs = []string{types.VerbList}
case types.KindDatabaseServer, types.KindDatabaseService, types.KindAppServer, types.KindKubeServer, types.KindWindowsDesktop, types.KindWindowsDesktopService:
default:
return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
}
if err := a.action(req.Namespace, req.ResourceType, actionVerbs...); err != nil {
return nil, trace.Wrap(err)
}
// Perform the label/search/expr filtering here (instead of at the backend
// `ListResources`) to ensure that it will be applied only to resources
// the user has access to.
filter := services.MatchResourceFilter{
ResourceKind: req.ResourceType,
Labels: req.Labels,
SearchKeywords: req.SearchKeywords,
PredicateExpression: req.PredicateExpression,
}
req.Labels = nil
req.SearchKeywords = nil
req.PredicateExpression = ""
resourceChecker, err := a.newResourceAccessChecker(req.ResourceType)
if err != nil {
return nil, trace.Wrap(err)
}
var resp types.ListResourcesResponse
if err := a.authServer.IterateResources(ctx, req, func(resource types.ResourceWithLabels) error {
if len(resp.Resources) == limit {
resp.NextKey = backend.GetPaginationKey(resource)
return ErrDone
}
if err := resourceChecker.CanAccess(resource); err != nil {
if trace.IsAccessDenied(err) {
return nil
}
return trace.Wrap(err)
}
switch match, err := services.MatchResourceByFilters(resource, filter, nil /* ignore dup matches */); {
case err != nil:
return trace.Wrap(err)
case match:
resp.Resources = append(resp.Resources, resource)
return nil
}
return nil
}); err != nil {
return nil, trace.Wrap(err)
}
return &resp, nil
}
// resourceAccessChecker allows access to be checked differently per resource type.
type resourceAccessChecker interface {
CanAccess(resource types.Resource) error
}
// resourceChecker is a pass through checker that utilizes the provided
// services.AccessChecker to check access
type resourceChecker struct {
services.AccessChecker
}
// CanAccess handles providing the proper services.AccessCheckable resource
// to the services.AccessChecker
func (r resourceChecker) CanAccess(resource types.Resource) error {
// MFA is not required for operations on app resources but
// will be enforced at the connection time.
state := services.AccessState{MFAVerified: true}
switch rr := resource.(type) {
case types.AppServer:
return r.CheckAccess(rr.GetApp(), state)
case types.KubeServer:
return r.CheckAccess(rr.GetCluster(), state)
case types.DatabaseServer:
return r.CheckAccess(rr.GetDatabase(), state)
case types.DatabaseService:
return r.CheckAccess(rr, state)
case types.Database:
return r.CheckAccess(rr, state)
case types.Server:
return r.CheckAccess(rr, state)
case types.WindowsDesktop:
return r.CheckAccess(rr, state)
case types.WindowsDesktopService:
return r.CheckAccess(rr, state)
default:
return trace.BadParameter("could not check access to resource type %T", r)
}
}
// newResourceAccessChecker creates a resourceAccessChecker for the provided resource type
func (a *ServerWithRoles) newResourceAccessChecker(resource string) (resourceAccessChecker, error) {
switch resource {
case types.KindAppServer, types.KindDatabaseServer, types.KindDatabaseService, types.KindWindowsDesktop, types.KindWindowsDesktopService, types.KindNode, types.KindKubeServer:
return &resourceChecker{AccessChecker: a.context.Checker}, nil
default:
return nil, trace.BadParameter("could not check access to resource type %s", resource)
}
}
// listResourcesWithSort retrieves all resources of a certain resource type with rbac applied
// then afterwards applies request sorting and filtering.
func (a *ServerWithRoles) listResourcesWithSort(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) {
if err := req.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
var resources []types.ResourceWithLabels
switch req.ResourceType {
case types.KindNode:
nodes, err := a.GetNodes(ctx, req.Namespace)
if err != nil {
return nil, trace.Wrap(err)
}
servers := types.Servers(nodes)
if err := servers.SortByCustom(req.SortBy); err != nil {
return nil, trace.Wrap(err)
}
resources = servers.AsResources()
case types.KindAppServer:
appservers, err := a.GetApplicationServers(ctx, req.Namespace)
if err != nil {
return nil, trace.Wrap(err)
}
servers := types.AppServers(appservers)
if err := servers.SortByCustom(req.SortBy); err != nil {
return nil, trace.Wrap(err)
}
resources = servers.AsResources()
case types.KindDatabaseServer:
dbservers, err := a.GetDatabaseServers(ctx, req.Namespace)
if err != nil {
return nil, trace.Wrap(err)
}
servers := types.DatabaseServers(dbservers)
if err := servers.SortByCustom(req.SortBy); err != nil {
return nil, trace.Wrap(err)
}
resources = servers.AsResources()
case types.KindKubernetesCluster:
kubeServers, err := a.GetKubernetesServers(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
// Extract kube clusters into its own list.
var clusters []types.KubeCluster
for _, svc := range kubeServers {
clusters = append(clusters, svc.GetCluster())
}
sortedClusters := types.KubeClusters(clusters)
if err := sortedClusters.SortByCustom(req.SortBy); err != nil {
return nil, trace.Wrap(err)
}
resources = sortedClusters.AsResources()
case types.KindKubeServer:
kubeServers, err := a.GetKubernetesServers(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
sortedServers := types.KubeServers(kubeServers)
if err := sortedServers.SortByCustom(req.SortBy); err != nil {
return nil, trace.Wrap(err)
}
resources = sortedServers.AsResources()
case types.KindWindowsDesktop:
windowsdesktops, err := a.GetWindowsDesktops(ctx, req.GetWindowsDesktopFilter())
if err != nil {
return nil, trace.Wrap(err)
}
desktops := types.WindowsDesktops(windowsdesktops)
if err := desktops.SortByCustom(req.SortBy); err != nil {
return nil, trace.Wrap(err)
}
resources = desktops.AsResources()
default:
return nil, trace.NotImplemented("resource type %q is not supported for listResourcesWithSort", req.ResourceType)
}
// Apply request filters and get pagination info.
resp, err := local.FakePaginate(resources, req)
if err != nil {
return nil, trace.Wrap(err)
}
return resp, nil
}
// ListWindowsDesktops not implemented: can only be called locally.
func (a *ServerWithRoles) ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) {
return nil, trace.NotImplemented(notImplementedMessage)
}
// ListWindowsDesktopServices not implemented: can only be called locally.
func (a *ServerWithRoles) ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error) {
return nil, trace.NotImplemented(notImplementedMessage)
}
func (a *ServerWithRoles) UpsertAuthServer(s types.Server) error {
if err := a.action(apidefaults.Namespace, types.KindAuthServer, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertAuthServer(s)
}
func (a *ServerWithRoles) GetAuthServers() ([]types.Server, error) {
if err := a.action(apidefaults.Namespace, types.KindAuthServer, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetAuthServers()
}
// DeleteAllAuthServers deletes all auth servers
func (a *ServerWithRoles) DeleteAllAuthServers() error {
if err := a.action(apidefaults.Namespace, types.KindAuthServer, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllAuthServers()
}
// DeleteAuthServer deletes auth server by name
func (a *ServerWithRoles) DeleteAuthServer(name string) error {
if err := a.action(apidefaults.Namespace, types.KindAuthServer, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAuthServer(name)
}
func (a *ServerWithRoles) UpsertProxy(s types.Server) error {
if err := a.action(apidefaults.Namespace, types.KindProxy, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertProxy(s)
}
func (a *ServerWithRoles) GetProxies() ([]types.Server, error) {
if err := a.action(apidefaults.Namespace, types.KindProxy, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetProxies()
}
// DeleteAllProxies deletes all proxies
func (a *ServerWithRoles) DeleteAllProxies() error {
if err := a.action(apidefaults.Namespace, types.KindProxy, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllProxies()
}
// DeleteProxy deletes proxy by name
func (a *ServerWithRoles) DeleteProxy(name string) error {
if err := a.action(apidefaults.Namespace, types.KindProxy, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteProxy(name)
}
func (a *ServerWithRoles) UpsertReverseTunnel(r types.ReverseTunnel) error {
if err := a.action(apidefaults.Namespace, types.KindReverseTunnel, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertReverseTunnel(r)
}
func (a *ServerWithRoles) GetReverseTunnel(name string, opts ...services.MarshalOption) (types.ReverseTunnel, error) {
if err := a.action(apidefaults.Namespace, types.KindReverseTunnel, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetReverseTunnel(name, opts...)
}
func (a *ServerWithRoles) GetReverseTunnels(ctx context.Context, opts ...services.MarshalOption) ([]types.ReverseTunnel, error) {
if err := a.action(apidefaults.Namespace, types.KindReverseTunnel, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetReverseTunnels(ctx, opts...)
}
func (a *ServerWithRoles) DeleteReverseTunnel(domainName string) error {
if err := a.action(apidefaults.Namespace, types.KindReverseTunnel, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteReverseTunnel(domainName)
}
func (a *ServerWithRoles) DeleteToken(ctx context.Context, token string) error {
if err := a.action(apidefaults.Namespace, types.KindToken, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteToken(ctx, token)
}
func (a *ServerWithRoles) GetTokens(ctx context.Context) ([]types.ProvisionToken, error) {
if err := a.action(apidefaults.Namespace, types.KindToken, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetTokens(ctx)
}
func (a *ServerWithRoles) GetToken(ctx context.Context, token string) (types.ProvisionToken, error) {
// The Proxy has permission to look up tokens by name in order to validate
// attempts to use the node join script.
if isProxy := a.hasBuiltinRole(types.RoleProxy); !isProxy {
if err := a.action(apidefaults.Namespace, types.KindToken, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetToken(ctx, token)
}
func enforceEnterpriseJoinMethodCreation(token types.ProvisionToken) error {
if modules.GetModules().BuildType() == modules.BuildEnterprise {
return nil
}
v, ok := token.(*types.ProvisionTokenV2)
if !ok {
return trace.BadParameter("unexpected token type %T", token)
}
if v.Spec.GitHub != nil && v.Spec.GitHub.EnterpriseServerHost != "" {
return fmt.Errorf(
"github enterprise server joining: %w",
ErrRequiresEnterprise,
)
}
return nil
}
// emitTokenEvent is called by Create/Upsert Token in order to emit any relevant
// events. For now, this just emits trusted_cluster_token.create.
func emitTokenEvent(ctx context.Context, e apievents.Emitter, token types.ProvisionToken) {
userMetadata := authz.ClientUserMetadata(ctx)
for _, role := range token.GetRoles() {
if role == types.RoleTrustedCluster {
if err := e.EmitAuditEvent(ctx, &apievents.TrustedClusterTokenCreate{
Metadata: apievents.Metadata{
Type: events.TrustedClusterTokenCreateEvent,
Code: events.TrustedClusterTokenCreateCode,
},
UserMetadata: userMetadata,
}); err != nil {
log.WithError(err).Warn("Failed to emit trusted cluster token create event.")
}
}
}
}
func (a *ServerWithRoles) UpsertToken(ctx context.Context, token types.ProvisionToken) error {
if err := a.action(apidefaults.Namespace, types.KindToken, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
if err := enforceEnterpriseJoinMethodCreation(token); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.UpsertToken(ctx, token); err != nil {
return trace.Wrap(err)
}
emitTokenEvent(ctx, a.authServer.emitter, token)
return nil
}
func (a *ServerWithRoles) CreateToken(ctx context.Context, token types.ProvisionToken) error {
if err := a.action(apidefaults.Namespace, types.KindToken, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := enforceEnterpriseJoinMethodCreation(token); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.CreateToken(ctx, token); err != nil {
return trace.Wrap(err)
}
emitTokenEvent(ctx, a.authServer.emitter, token)
return nil
}
// ChangePassword updates users password based on the old password.
func (a *ServerWithRoles) ChangePassword(
ctx context.Context,
req *proto.ChangePasswordRequest,
) error {
if err := a.currentUserAction(req.User); err != nil {
return trace.Wrap(err)
}
return a.authServer.ChangePassword(ctx, req)
}
func (a *ServerWithRoles) PreAuthenticatedSignIn(ctx context.Context, user string) (types.WebSession, error) {
if err := a.currentUserAction(user); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.PreAuthenticatedSignIn(ctx, user, a.context.Identity.GetIdentity())
}
// CreateWebSession creates a new web session for the specified user
func (a *ServerWithRoles) CreateWebSession(ctx context.Context, user string) (types.WebSession, error) {
if err := a.currentUserAction(user); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.CreateWebSession(ctx, user)
}
// ExtendWebSession creates a new web session for a user based on a valid previous session.
// Additional roles are appended to initial roles if there is an approved access request.
// The new session expiration time will not exceed the expiration time of the old session.
func (a *ServerWithRoles) ExtendWebSession(ctx context.Context, req WebSessionReq) (types.WebSession, error) {
if err := a.currentUserAction(req.User); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.ExtendWebSession(ctx, req, a.context.Identity.GetIdentity())
}
// GetWebSessionInfo returns the web session for the given user specified with sid.
// The session is stripped of any authentication details.
// Implements auth.WebUIService
func (a *ServerWithRoles) GetWebSessionInfo(ctx context.Context, user, sessionID string) (types.WebSession, error) {
if err := a.currentUserAction(user); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetWebSessionInfo(ctx, user, sessionID)
}
// GetWebSession returns the web session specified with req.
// Implements auth.ReadAccessPoint.
func (a *ServerWithRoles) GetWebSession(ctx context.Context, req types.GetWebSessionRequest) (types.WebSession, error) {
return a.WebSessions().Get(ctx, req)
}
// WebSessions returns the web session manager.
// Implements services.WebSessionsGetter.
func (a *ServerWithRoles) WebSessions() types.WebSessionInterface {
return &webSessionsWithRoles{c: a, ws: a.authServer.WebSessions()}
}
// Get returns the web session specified with req.
func (r *webSessionsWithRoles) Get(ctx context.Context, req types.GetWebSessionRequest) (types.WebSession, error) {
if err := r.c.currentUserAction(req.User); err != nil {
if err := r.c.action(apidefaults.Namespace, types.KindWebSession, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return r.ws.Get(ctx, req)
}
// List returns the list of all web sessions.
func (r *webSessionsWithRoles) List(ctx context.Context) ([]types.WebSession, error) {
if err := r.c.action(apidefaults.Namespace, types.KindWebSession, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
if err := r.c.action(apidefaults.Namespace, types.KindWebSession, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return r.ws.List(ctx)
}
// Upsert creates a new or updates the existing web session from the specified session.
// TODO(dmitri): this is currently only implemented for local invocations. This needs to be
// moved into a more appropriate API
func (*webSessionsWithRoles) Upsert(ctx context.Context, session types.WebSession) error {
return trace.NotImplemented(notImplementedMessage)
}
// Delete removes the web session specified with req.
func (r *webSessionsWithRoles) Delete(ctx context.Context, req types.DeleteWebSessionRequest) error {
if err := r.c.canDeleteWebSession(req.User); err != nil {
return trace.Wrap(err)
}
return r.ws.Delete(ctx, req)
}
// DeleteAll removes all web sessions.
func (r *webSessionsWithRoles) DeleteAll(ctx context.Context) error {
if err := r.c.action(apidefaults.Namespace, types.KindWebSession, types.VerbList); err != nil {
return trace.Wrap(err)
}
if err := r.c.action(apidefaults.Namespace, types.KindWebSession, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return r.ws.DeleteAll(ctx)
}
// GetWebToken returns the web token specified with req.
// Implements auth.ReadAccessPoint.
func (a *ServerWithRoles) GetWebToken(ctx context.Context, req types.GetWebTokenRequest) (types.WebToken, error) {
return a.WebTokens().Get(ctx, req)
}
type webSessionsWithRoles struct {
c accessChecker
ws types.WebSessionInterface
}
// WebTokens returns the web token manager.
// Implements services.WebTokensGetter.
func (a *ServerWithRoles) WebTokens() types.WebTokenInterface {
return &webTokensWithRoles{c: a, t: a.authServer.WebTokens()}
}
// Get returns the web token specified with req.
func (r *webTokensWithRoles) Get(ctx context.Context, req types.GetWebTokenRequest) (types.WebToken, error) {
if err := r.c.currentUserAction(req.User); err != nil {
if err := r.c.action(apidefaults.Namespace, types.KindWebToken, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return r.t.Get(ctx, req)
}
// List returns the list of all web tokens.
func (r *webTokensWithRoles) List(ctx context.Context) ([]types.WebToken, error) {
if err := r.c.action(apidefaults.Namespace, types.KindWebToken, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
return r.t.List(ctx)
}
// Upsert creates a new or updates the existing web token from the specified token.
// TODO(dmitri): this is currently only implemented for local invocations. This needs to be
// moved into a more appropriate API
func (*webTokensWithRoles) Upsert(ctx context.Context, session types.WebToken) error {
return trace.NotImplemented(notImplementedMessage)
}
// Delete removes the web token specified with req.
func (r *webTokensWithRoles) Delete(ctx context.Context, req types.DeleteWebTokenRequest) error {
if err := r.c.currentUserAction(req.User); err != nil {
if err := r.c.action(apidefaults.Namespace, types.KindWebToken, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
}
return r.t.Delete(ctx, req)
}
// DeleteAll removes all web tokens.
func (r *webTokensWithRoles) DeleteAll(ctx context.Context) error {
if err := r.c.action(apidefaults.Namespace, types.KindWebToken, types.VerbList); err != nil {
return trace.Wrap(err)
}
if err := r.c.action(apidefaults.Namespace, types.KindWebToken, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return r.t.DeleteAll(ctx)
}
type webTokensWithRoles struct {
c accessChecker
t types.WebTokenInterface
}
type accessChecker interface {
action(namespace, resource string, verbs ...string) error
currentUserAction(user string) error
canDeleteWebSession(username string) error
}
func (a *ServerWithRoles) GetAccessRequests(ctx context.Context, filter types.AccessRequestFilter) ([]types.AccessRequest, error) {
// users can always view their own access requests
if filter.User != "" && a.currentUserAction(filter.User) == nil {
return a.authServer.GetAccessRequests(ctx, filter)
}
// users with read + list permissions can get all requests
if a.withOptions(quietAction(true)).action(apidefaults.Namespace, types.KindAccessRequest, types.VerbList) == nil {
if a.withOptions(quietAction(true)).action(apidefaults.Namespace, types.KindAccessRequest, types.VerbRead) == nil {
return a.authServer.GetAccessRequests(ctx, filter)
}
}
// user does not have read/list permissions and is not specifically requesting only
// their own requests. we therefore subselect the filter results to show only those requests
// that the user *is* allowed to see (specifically, their own requests + requests that they
// are allowed to review).
checker, err := services.NewReviewPermissionChecker(ctx, a.authServer, a.context.User.GetName())
if err != nil {
return nil, trace.Wrap(err)
}
// unless the user has allow directives for reviewing, they will never be able to
// see any requests other than their own.
if !checker.HasAllowDirectives() {
if filter.User != "" {
// filter specifies a user, but it wasn't caught by the preceding exception,
// so just return nothing.
return nil, nil
}
filter.User = a.context.User.GetName()
return a.authServer.GetAccessRequests(ctx, filter)
}
reqs, err := a.authServer.GetAccessRequests(ctx, filter)
if err != nil {
return nil, trace.Wrap(err)
}
// filter in place
filtered := reqs[:0]
for _, req := range reqs {
if req.GetUser() == a.context.User.GetName() {
filtered = append(filtered, req)
continue
}
ok, err := checker.CanReviewRequest(req)
if err != nil {
return nil, trace.Wrap(err)
}
if ok {
filtered = append(filtered, req)
continue
}
}
return filtered, nil
}
func (a *ServerWithRoles) CreateAccessRequest(ctx context.Context, req types.AccessRequest) error {
// An exception is made to allow users to create access *pending* requests for themselves.
if !req.GetState().IsPending() || a.currentUserAction(req.GetUser()) != nil {
if err := a.action(apidefaults.Namespace, types.KindAccessRequest, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
}
return a.authServer.CreateAccessRequest(ctx, req, a.context.Identity.GetIdentity())
}
func (a *ServerWithRoles) SetAccessRequestState(ctx context.Context, params types.AccessRequestUpdate) error {
if err := a.action(apidefaults.Namespace, types.KindAccessRequest, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.SetAccessRequestState(ctx, params)
}
func (a *ServerWithRoles) SubmitAccessReview(ctx context.Context, params types.AccessReviewSubmission) (types.AccessRequest, error) {
// review author defaults to username of caller.
if params.Review.Author == "" {
params.Review.Author = a.context.User.GetName()
}
// review author must match calling user, except in the case of the builtin admin role. we make this
// exception in order to allow for convenient testing with local tctl connections.
if !a.hasBuiltinRole(types.RoleAdmin) {
if params.Review.Author != a.context.User.GetName() {
return nil, trace.AccessDenied("user %q cannot submit reviews on behalf of %q", a.context.User.GetName(), params.Review.Author)
}
// MaybeCanReviewRequests returns false positives, but it will tell us
// if the user definitely can't review requests, which saves a lot of work.
if !a.context.Checker.MaybeCanReviewRequests() {
return nil, trace.AccessDenied("user %q cannot submit reviews", a.context.User.GetName())
}
}
// note that we haven't actually enforced any access-control other than requiring
// the author field to match the calling user. fine-grained permissions are evaluated
// under optimistic locking at the level of the backend service. the correctness of the
// author field is all that need be enforced at this level.
return a.authServer.SubmitAccessReview(ctx, params)
}
func (a *ServerWithRoles) GetAccessCapabilities(ctx context.Context, req types.AccessCapabilitiesRequest) (*types.AccessCapabilities, error) {
// default to checking the capabilities of the caller
if req.User == "" {
req.User = a.context.User.GetName()
}
// all users can check their own capabilities
if a.currentUserAction(req.User) != nil {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
if err := a.action(apidefaults.Namespace, types.KindRole, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetAccessCapabilities(ctx, req)
}
// GetPluginData loads all plugin data matching the supplied filter.
func (a *ServerWithRoles) GetPluginData(ctx context.Context, filter types.PluginDataFilter) ([]types.PluginData, error) {
switch filter.Kind {
case types.KindAccessRequest:
// for backwards compatibility, we allow list/read against access requests to also grant list/read for
// access request related plugin data.
if a.withOptions(quietAction(true)).action(apidefaults.Namespace, types.KindAccessRequest, types.VerbList) != nil {
if err := a.action(apidefaults.Namespace, types.KindAccessPluginData, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
}
if a.withOptions(quietAction(true)).action(apidefaults.Namespace, types.KindAccessRequest, types.VerbRead) != nil {
if err := a.action(apidefaults.Namespace, types.KindAccessPluginData, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetPluginData(ctx, filter)
default:
return nil, trace.BadParameter("unsupported resource kind %q", filter.Kind)
}
}
// UpdatePluginData updates a per-resource PluginData entry.
func (a *ServerWithRoles) UpdatePluginData(ctx context.Context, params types.PluginDataUpdateParams) error {
switch params.Kind {
case types.KindAccessRequest:
// for backwards compatibility, we allow update against access requests to also grant update for
// access request related plugin data.
if a.withOptions(quietAction(true)).action(apidefaults.Namespace, types.KindAccessRequest, types.VerbUpdate) != nil {
if err := a.action(apidefaults.Namespace, types.KindAccessPluginData, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
}
return a.authServer.UpdatePluginData(ctx, params)
default:
return trace.BadParameter("unsupported resource kind %q", params.Kind)
}
}
// pingCacheKey is used to with Server.ttlCache to cache frequently-loaded
// values in the Ping method.
type pingCacheKey struct {
kind string
}
// Ping gets basic info about the auth server.
func (a *ServerWithRoles) Ping(ctx context.Context) (proto.PingResponse, error) {
// The Ping method does not require special permissions since it only returns
// basic status information. This is an intentional design choice. Alternative
// methods should be used for relaying any sensitive information.
cn, err := a.authServer.GetClusterName()
if err != nil {
return proto.PingResponse{}, trace.Wrap(err)
}
// license check result is loaded from real backend, and is not modified once loaded, so we
// can save a lot of IO by doing short-lived ttl caching.
heartbeat, err := utils.FnCacheGet(ctx, a.authServer.ttlCache, pingCacheKey{"license-check-result"}, a.authServer.GetLicenseCheckResult)
if err != nil {
log.Warnf("Failed to load license check result for Ping: %v", err)
}
var warnings []string
if heartbeat != nil {
for _, notification := range heartbeat.Spec.Notifications {
if notification.Type == LicenseExpiredNotification {
warnings = append(warnings, notification.Text)
}
}
}
return proto.PingResponse{
ClusterName: cn.GetClusterName(),
ServerVersion: teleport.Version,
ServerFeatures: modules.GetModules().Features().ToProto(),
ProxyPublicAddr: a.getProxyPublicAddr(),
IsBoring: modules.GetModules().IsBoringBinary(),
LicenseWarnings: warnings,
LoadAllCAs: a.authServer.loadAllCAs,
}, nil
}
// getProxyPublicAddr gets the server's public proxy address.
func (a *ServerWithRoles) getProxyPublicAddr() string {
if proxies, err := a.authServer.GetProxies(); err == nil {
for _, p := range proxies {
addr := p.GetPublicAddr()
if addr == "" {
continue
}
if _, err := utils.ParseAddr(addr); err != nil {
log.Warningf("Invalid public address on the proxy %q: %q: %v.", p.GetName(), addr, err)
continue
}
return addr
}
}
return ""
}
func (a *ServerWithRoles) DeleteAccessRequest(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindAccessRequest, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAccessRequest(ctx, name)
}
func (a *ServerWithRoles) GetUsers(withSecrets bool) ([]types.User, error) {
if withSecrets {
// TODO(fspmarshall): replace admin requirement with VerbReadWithSecrets once we've
// migrated to that model.
if !a.hasBuiltinRole(types.RoleAdmin) {
err := trace.AccessDenied("user %q requested access to all users with secrets", a.context.User.GetName())
log.Warning(err)
if err := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.UserLogin{
Metadata: apievents.Metadata{
Type: events.UserLoginEvent,
Code: events.UserLocalLoginFailureCode,
},
Method: events.LoginMethodClientCert,
Status: apievents.Status{
Success: false,
Error: trace.Unwrap(err).Error(),
UserMessage: err.Error(),
},
}); err != nil {
log.WithError(err).Warn("Failed to emit local login failure event.")
}
return nil, trace.AccessDenied("this request can be only executed by an admin")
}
} else {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetUsers(withSecrets)
}
func (a *ServerWithRoles) GetUser(name string, withSecrets bool) (types.User, error) {
if withSecrets {
// TODO(fspmarshall): replace admin requirement with VerbReadWithSecrets once we've
// migrated to that model.
if !a.hasBuiltinRole(types.RoleAdmin) {
err := trace.AccessDenied("user %q requested access to user %q with secrets", a.context.User.GetName(), name)
log.Warning(err)
if err := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.UserLogin{
Metadata: apievents.Metadata{
Type: events.UserLoginEvent,
Code: events.UserLocalLoginFailureCode,
},
Method: events.LoginMethodClientCert,
Status: apievents.Status{
Success: false,
Error: trace.Unwrap(err).Error(),
UserMessage: err.Error(),
},
}); err != nil {
log.WithError(err).Warn("Failed to emit local login failure event.")
}
return nil, trace.AccessDenied("this request can be only executed by an admin")
}
} else {
// if secrets are not being accessed, let users always read
// their own info.
if err := a.currentUserAction(name); err != nil {
// not current user, perform normal permission check.
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
}
return a.authServer.GetUser(name, withSecrets)
}
// GetCurrentUser returns current user as seen by the server.
// Useful especially in the context of remote clusters which perform role and trait mapping.
func (a *ServerWithRoles) GetCurrentUser(ctx context.Context) (types.User, error) {
// check access to roles
for _, role := range a.context.User.GetRoles() {
_, err := a.GetRole(ctx, role)
if err != nil {
return nil, trace.Wrap(err)
}
}
usrRes := a.context.User.WithoutSecrets()
if usr, ok := usrRes.(types.User); ok {
return usr, nil
}
return nil, trace.BadParameter("expected types.User when fetching current user information, got %T", usrRes)
}
// GetCurrentUserRoles returns current user's roles.
func (a *ServerWithRoles) GetCurrentUserRoles(ctx context.Context) ([]types.Role, error) {
roleNames := a.context.User.GetRoles()
roles := make([]types.Role, 0, len(roleNames))
for _, roleName := range roleNames {
role, err := a.GetRole(ctx, roleName)
if err != nil {
return nil, trace.Wrap(err)
}
roles = append(roles, role)
}
return roles, nil
}
// DeleteUser deletes an existng user in a backend by username.
func (a *ServerWithRoles) DeleteUser(ctx context.Context, user string) error {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteUser(ctx, user)
}
func (a *ServerWithRoles) GenerateHostCert(
ctx context.Context, key []byte, hostID, nodeName string, principals []string, clusterName string, role types.SystemRole, ttl time.Duration,
) ([]byte, error) {
serviceContext := services.Context{
User: a.context.User,
HostCert: &services.HostCertContext{
HostID: hostID,
NodeName: nodeName,
Principals: principals,
ClusterName: clusterName,
Role: role,
TTL: ttl,
},
}
// Instead of the usual RBAC checks, we'll manually call CheckAccessToRule
// here as we'll be evaluating `where` predicates with a custom RuleContext
// to expose cert request fields.
// We've only got a single verb to check so luckily it's pretty concise.
if err := a.withOptions().context.Checker.CheckAccessToRule(
&serviceContext, apidefaults.Namespace, types.KindHostCert, types.VerbCreate, false,
); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GenerateHostCert(ctx, key, hostID, nodeName, principals, clusterName, role, ttl)
}
// NewKeepAliver not implemented: can only be called locally.
func (a *ServerWithRoles) NewKeepAliver(ctx context.Context) (types.KeepAliver, error) {
return nil, trace.NotImplemented(notImplementedMessage)
}
// desiredAccessInfo inspects the current request to determine which access
// information (roles, traits, and allowed resource IDs) the requesting user
// wants to be present on the resulting certificate. This does not attempt to
// determine if the user is allowed to assume the returned roles. Will set
// `req.AccessRequests` and potentially shorten `req.Expires` based on the
// access request expirations.
func (a *ServerWithRoles) desiredAccessInfo(ctx context.Context, req *proto.UserCertsRequest, user types.User) (*services.AccessInfo, error) {
if req.Username != a.context.User.GetName() {
if req.UseRoleRequests || len(req.RoleRequests) > 0 {
err := trace.AccessDenied("User %v tried to issue a cert for %v and added role requests. This is not supported.", a.context.User.GetName(), req.Username)
log.WithError(err).Warn()
return nil, err
}
if len(req.AccessRequests) > 0 {
err := trace.AccessDenied("User %v tried to issue a cert for %v and added access requests. This is not supported.", a.context.User.GetName(), req.Username)
log.WithError(err).Warn()
return nil, err
}
return a.desiredAccessInfoForImpersonation(req, user)
}
if req.UseRoleRequests || len(req.RoleRequests) > 0 {
if len(req.AccessRequests) > 0 {
err := trace.AccessDenied("User %v tried to issue a cert with both role and access requests. This is not supported.", a.context.User.GetName())
log.WithError(err).Warn()
return nil, err
}
return a.desiredAccessInfoForRoleRequest(req, user.GetTraits())
}
return a.desiredAccessInfoForUser(ctx, req, user)
}
// desiredAccessInfoForImpersonation returns the desired AccessInfo for an
// impersonation request.
func (a *ServerWithRoles) desiredAccessInfoForImpersonation(req *proto.UserCertsRequest, user types.User) (*services.AccessInfo, error) {
return &services.AccessInfo{
Roles: user.GetRoles(),
Traits: user.GetTraits(),
}, nil
}
// desiredAccessInfoForRoleRequest returns the desired roles for a role request.
func (a *ServerWithRoles) desiredAccessInfoForRoleRequest(req *proto.UserCertsRequest, traits wrappers.Traits) (*services.AccessInfo, error) {
// If UseRoleRequests is set, make sure we don't return unusable certs: an
// identity without roles can't be parsed.
// DEPRECATED: consider making role requests without UseRoleRequests set an
// error in V11.
if req.UseRoleRequests && len(req.RoleRequests) == 0 {
return nil, trace.BadParameter("at least one role request is required")
}
// If role requests are provided, attempt to satisfy them instead of
// pulling them directly from the logged in identity. Role requests
// are intended to reduce allowed permissions so we'll accept them
// as-is for now (and ensure the user is allowed to assume them
// later).
//
// Traits are copied across from the impersonating user so that role
// variables within the impersonated role behave as expected.
return &services.AccessInfo{
Roles: req.RoleRequests,
Traits: traits,
}, nil
}
// desiredAccessInfoForUser returns the desired AccessInfo
// cert request which may contain access requests.
func (a *ServerWithRoles) desiredAccessInfoForUser(ctx context.Context, req *proto.UserCertsRequest, user types.User) (*services.AccessInfo, error) {
currentIdentity := a.context.Identity.GetIdentity()
// Start with the base AccessInfo for current logged-in identity, before
// considering new or dropped access requests. This will include roles from
// currently assumed role access requests, and allowed resources from
// currently assumed resource access requests.
accessInfo, err := services.AccessInfoFromLocalIdentity(currentIdentity, a)
if err != nil {
return nil, trace.Wrap(err)
}
if dropAnyRequests := len(req.DropAccessRequests) > 0; dropAnyRequests {
// Reset to the base roles and traits stored in the backend user,
// currently active requests (not being dropped) and new access requests
// will be filled in below.
accessInfo = services.AccessInfoFromUser(user)
// Check for ["*"] as special case to drop all requests.
if len(req.DropAccessRequests) == 1 && req.DropAccessRequests[0] == "*" {
// Removing all access requests from cert, return early with base roles
// for the user. Make sure to clear req.AccessRequests, or these will be
// encoded in the cert.
req.AccessRequests = nil
return accessInfo, nil
}
}
// Build a list of access request IDs which we need to fetch and apply to
// the new cert request.
var finalRequestIDs []string
for _, requestList := range [][]string{currentIdentity.ActiveRequests, req.AccessRequests} {
for _, reqID := range requestList {
if !slices.Contains(req.DropAccessRequests, reqID) {
finalRequestIDs = append(finalRequestIDs, reqID)
}
}
}
finalRequestIDs = apiutils.Deduplicate(finalRequestIDs)
// Replace req.AccessRequests with final filtered values, these will be
// encoded into the cert.
req.AccessRequests = finalRequestIDs
// Reset the resource restrictions, we are going to iterate all access
// requests below, if there are any resource requests this will be set.
accessInfo.AllowedResourceIDs = nil
for _, reqID := range finalRequestIDs {
// Fetch and validate the access request for this user.
accessRequest, err := a.authServer.getValidatedAccessRequest(ctx, currentIdentity, req.Username, reqID)
if err != nil {
return nil, trace.Wrap(err)
}
// Cannot generate a cert that would outlive the access request.
if accessRequest.GetAccessExpiry().Before(req.Expires) {
req.Expires = accessRequest.GetAccessExpiry()
}
// Merge requested roles from all access requests.
accessInfo.Roles = append(accessInfo.Roles, accessRequest.GetRoles()...)
// Make sure only 1 access request includes resource restrictions. There
// is not a logical way to merge resource access requests, e.g. if a
// user requested "node1" with role "user" and "node2" with role "admin".
if requestedResourceIDs := accessRequest.GetRequestedResourceIDs(); len(requestedResourceIDs) > 0 {
if len(accessInfo.AllowedResourceIDs) > 0 {
return nil, trace.BadParameter("cannot generate certificate with multiple resource access requests")
}
accessInfo.AllowedResourceIDs = requestedResourceIDs
}
}
accessInfo.Roles = apiutils.Deduplicate(accessInfo.Roles)
return accessInfo, nil
}
// GenerateUserCerts generates users certificates
func (a *ServerWithRoles) GenerateUserCerts(ctx context.Context, req proto.UserCertsRequest) (*proto.Certs, error) {
identity := a.context.Identity.GetIdentity()
return a.generateUserCerts(
ctx, req,
certRequestDeviceExtensions(identity.DeviceExtensions),
)
}
func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserCertsRequest, opts ...certRequestOption) (*proto.Certs, error) {
// Device trust: authorize device before issuing certificates.
authPref, err := a.authServer.GetAuthPreference(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.verifyUserDeviceForCertIssuance(req.Usage, authPref.GetDeviceTrust()); err != nil {
return nil, trace.Wrap(err)
}
var verifiedMFADeviceID string
if req.MFAResponse != nil {
dev, _, err := a.authServer.validateMFAAuthResponse(
ctx, req.GetMFAResponse(), req.Username, false /* passwordless */)
if err != nil {
return nil, trace.Wrap(err)
}
verifiedMFADeviceID = dev.Id
}
// this prevents clients who have no chance at getting a cert and impersonating anyone
// from enumerating local users and hitting database
if !a.hasBuiltinRole(types.RoleAdmin) && !a.context.Checker.CanImpersonateSomeone() && req.Username != a.context.User.GetName() {
return nil, trace.AccessDenied("access denied: impersonation is not allowed")
}
if a.context.Identity.GetIdentity().DisallowReissue {
return nil, trace.AccessDenied("access denied: identity is not allowed to reissue certificates")
}
// Prohibit recursive impersonation behavior:
//
// Alice can impersonate Bob
// Bob can impersonate Grace <- this code block prohibits the escape
//
// Allow cases:
//
// Alice can impersonate Bob
//
// Bob (impersonated by Alice) can renew the cert with route to cluster
//
// Similarly, for role requests, Alice is allowed to request roles `access`
// and `ci`, however these impersonated identities, Alice(access) and
// Alice(ci), should not be able to issue any new certificates.
//
if a.context.Identity != nil && a.context.Identity.GetIdentity().Impersonator != "" {
if len(req.AccessRequests) > 0 {
return nil, trace.AccessDenied("access denied: impersonated user can not request new roles")
}
if req.UseRoleRequests || len(req.RoleRequests) > 0 {
// Note: technically this should never be needed as all role
// impersonated certs should have the DisallowReissue set.
return nil, trace.AccessDenied("access denied: impersonated roles can not request other roles")
}
if req.Username != a.context.User.GetName() {
return nil, trace.AccessDenied("access denied: impersonated user can not impersonate anyone else")
}
}
// Extract the user and role set for whom the certificate will be generated.
// This should be safe since this is typically done against a local user.
//
// This call bypasses RBAC check for users read on purpose.
// Users who are allowed to impersonate other users might not have
// permissions to read user data.
user, err := a.authServer.GetUser(req.Username, false)
if err != nil {
log.WithError(err).Debugf("Could not impersonate user %v. The user could not be fetched from local store.", req.Username)
return nil, trace.AccessDenied("access denied")
}
// Do not allow SSO users to be impersonated.
if req.Username != a.context.User.GetName() && user.GetCreatedBy().Connector != nil {
log.Warningf("User %v tried to issue a cert for externally managed user %v, this is not supported.", a.context.User.GetName(), req.Username)
return nil, trace.AccessDenied("access denied")
}
// For users renewing certificates limit the TTL to the duration of the session, to prevent
// users renewing certificates forever.
if req.Username == a.context.User.GetName() {
identity := a.context.Identity.GetIdentity()
sessionExpires := identity.Expires
if sessionExpires.IsZero() {
log.Warningf("Encountered identity with no expiry: %v and denied request. Must be internal logic error.", a.context.Identity)
return nil, trace.AccessDenied("access denied")
}
if req.Expires.Before(a.authServer.GetClock().Now()) {
return nil, trace.AccessDenied("access denied: client credentials have expired, please relogin.")
}
// if these credentials are not renewable, we limit the TTL to the duration of the session
// (this prevents users renewing their certificates forever)
if req.Expires.After(sessionExpires) {
if !identity.Renewable {
req.Expires = sessionExpires
} else if max := a.authServer.GetClock().Now().Add(defaults.MaxRenewableCertTTL); req.Expires.After(max) {
req.Expires = max
}
}
}
// we're going to extend the roles list based on the access requests, so we
// ensure that all the current requests are added to the new certificate
// (and are checked again)
req.AccessRequests = append(req.AccessRequests, a.context.Identity.GetIdentity().ActiveRequests...)
if req.Username != a.context.User.GetName() && len(req.AccessRequests) > 0 {
return nil, trace.AccessDenied("user %q requested cert for %q and included access requests, this is not supported.", a.context.User.GetName(), req.Username)
}
accessInfo, err := a.desiredAccessInfo(ctx, &req, user)
if err != nil {
return nil, trace.Wrap(err)
}
parsedRoles, err := services.FetchRoleList(accessInfo.Roles, a.authServer, accessInfo.Traits)
if err != nil {
return nil, trace.Wrap(err)
}
// add implicit roles to the set and build a checker
roleSet := services.NewRoleSet(parsedRoles...)
clusterName, err := a.GetClusterName()
if err != nil {
return nil, trace.Wrap(err)
}
checker := services.NewAccessCheckerWithRoleSet(accessInfo, clusterName.GetClusterName(), roleSet)
switch {
case a.hasBuiltinRole(types.RoleAdmin):
// builtin admins can impersonate anyone
// this is required for local tctl commands to work
case req.Username == a.context.User.GetName():
// users can impersonate themselves, but role impersonation requests
// must be checked.
if len(req.RoleRequests) > 0 {
// Note: CheckImpersonateRoles() checks against the _stored_
// impersonate roles for the user rather than the set available
// to the current identity. If not explicitly denied (as above),
// this could allow a role-impersonated certificate to request new
// certificates with alternate RoleRequests.
err = a.context.Checker.CheckImpersonateRoles(a.context.User, parsedRoles)
if err != nil {
log.Warning(err)
err := trace.AccessDenied("user %q has requested role impersonation for %q", a.context.User.GetName(), accessInfo.Roles)
if err := a.authServer.emitter.EmitAuditEvent(a.CloseContext(), &apievents.UserLogin{
Metadata: apievents.Metadata{
Type: events.UserLoginEvent,
Code: events.UserLocalLoginFailureCode,
},
Method: events.LoginMethodClientCert,
Status: apievents.Status{
Success: false,
Error: trace.Unwrap(err).Error(),
UserMessage: err.Error(),
},
}); err != nil {
log.WithError(err).Warn("Failed to emit local login failure event.")
}
return nil, trace.Wrap(err)
}
}
default:
// check if this user is allowed to impersonate other users
err = a.context.Checker.CheckImpersonate(a.context.User, user, parsedRoles)
// adjust session TTL based on the impersonated role set limit
ttl := req.Expires.Sub(a.authServer.GetClock().Now())
ttl = checker.AdjustSessionTTL(ttl)
req.Expires = a.authServer.GetClock().Now().Add(ttl)
if err != nil {
log.Warning(err)
err := trace.AccessDenied("user %q has requested to generate certs for %q.", a.context.User.GetName(), accessInfo.Roles)
if err := a.authServer.emitter.EmitAuditEvent(a.CloseContext(), &apievents.UserLogin{
Metadata: apievents.Metadata{
Type: events.UserLoginEvent,
Code: events.UserLocalLoginFailureCode,
},
Method: events.LoginMethodClientCert,
Status: apievents.Status{
Success: false,
Error: trace.Unwrap(err).Error(),
UserMessage: err.Error(),
},
}); err != nil {
log.WithError(err).Warn("Failed to emit local login failure event.")
}
return nil, trace.Wrap(err)
}
}
// Generate certificate, note that the roles TTL will be ignored because
// the request is coming from "tctl auth sign" itself.
certReq := certRequest{
mfaVerified: verifiedMFADeviceID,
user: user,
ttl: req.Expires.Sub(a.authServer.GetClock().Now()),
compatibility: req.Format,
publicKey: req.PublicKey,
overrideRoleTTL: a.hasBuiltinRole(types.RoleAdmin),
routeToCluster: req.RouteToCluster,
kubernetesCluster: req.KubernetesCluster,
dbService: req.RouteToDatabase.ServiceName,
dbProtocol: req.RouteToDatabase.Protocol,
dbUser: req.RouteToDatabase.Username,
dbName: req.RouteToDatabase.Database,
appName: req.RouteToApp.Name,
appSessionID: req.RouteToApp.SessionID,
appPublicAddr: req.RouteToApp.PublicAddr,
appClusterName: req.RouteToApp.ClusterName,
awsRoleARN: req.RouteToApp.AWSRoleARN,
azureIdentity: req.RouteToApp.AzureIdentity,
gcpServiceAccount: req.RouteToApp.GCPServiceAccount,
checker: checker,
// Copy IP from current identity to the generated certificate, if present,
// to avoid generateUserCerts() being used to drop IP pinning in the new certificates.
loginIP: a.context.Identity.GetIdentity().LoginIP,
traits: accessInfo.Traits,
activeRequests: services.RequestIDs{
AccessRequests: req.AccessRequests,
},
connectionDiagnosticID: req.ConnectionDiagnosticID,
}
if user.GetName() != a.context.User.GetName() {
certReq.impersonator = a.context.User.GetName()
} else if req.UseRoleRequests || len(req.RoleRequests) > 0 {
// Role impersonation uses the user's own name as the impersonator value.
certReq.impersonator = a.context.User.GetName()
// Deny reissuing certs to prevent privilege re-escalation.
certReq.disallowReissue = true
} else if a.context.Identity != nil && a.context.Identity.GetIdentity().Impersonator != "" {
// impersonating users can receive new certs
certReq.impersonator = a.context.Identity.GetIdentity().Impersonator
}
switch req.Usage {
case proto.UserCertsRequest_Database:
certReq.usage = []string{teleport.UsageDatabaseOnly}
case proto.UserCertsRequest_App:
certReq.usage = []string{teleport.UsageAppsOnly}
case proto.UserCertsRequest_Kubernetes:
certReq.usage = []string{teleport.UsageKubeOnly}
case proto.UserCertsRequest_SSH:
// SSH certs are ssh-only by definition, certReq.usage only applies to
// TLS certs.
case proto.UserCertsRequest_All:
// Unrestricted usage.
case proto.UserCertsRequest_WindowsDesktop:
// Desktop certs.
certReq.usage = []string{teleport.UsageWindowsDesktopOnly}
default:
return nil, trace.BadParameter("unsupported cert usage %q", req.Usage)
}
for _, o := range opts {
o(&certReq)
}
// If the user is renewing a renewable cert, make sure the renewable flag
// remains for subsequent requests of the primary certificate. The
// renewable flag should never be carried over for impersonation, role
// requests, or when the disallow-reissue flag has already been set.
if a.context.Identity.GetIdentity().Renewable &&
req.Username == a.context.User.GetName() &&
len(req.RoleRequests) == 0 &&
!req.UseRoleRequests &&
!certReq.disallowReissue {
certReq.renewable = true
}
// If the cert is renewable, process any certificate generation counter.
if certReq.renewable {
currentIdentityGeneration := a.context.Identity.GetIdentity().Generation
if err := a.authServer.validateGenerationLabel(ctx, user, &certReq, currentIdentityGeneration); err != nil {
return nil, trace.Wrap(err)
}
}
certs, err := a.authServer.generateUserCert(certReq)
if err != nil {
return nil, trace.Wrap(err)
}
return certs, nil
}
// verifyUserDeviceForCertIssuance verifies if the user device is a trusted
// device, in accordance to the certificate usage and the cluster's DeviceTrust
// settings. It's meant to be called before issuing new user certificates.
// Each Node (or access server) verifies the device independently, so this check
// is not paramount to the access system itself, but it stops bad attempts from
// progressing further and provides better feedback than other protocol-specific
// failures.
func (a *ServerWithRoles) verifyUserDeviceForCertIssuance(usage proto.UserCertsRequest_CertUsage, dt *types.DeviceTrust) error {
// Ignore App or WindowsDeskop requests, they do not support device trust.
if usage == proto.UserCertsRequest_App || usage == proto.UserCertsRequest_WindowsDesktop {
return nil
}
identity := a.context.Identity.GetIdentity()
return trace.Wrap(dtauthz.VerifyTLSUser(dt, identity))
}
// CreateBot creates a new certificate renewal bot and returns a join token.
func (a *ServerWithRoles) CreateBot(ctx context.Context, req *proto.CreateBotRequest) (*proto.CreateBotResponse, error) {
// Note: this creates a role with role impersonation privileges for all
// roles listed in the request and doesn't attempt to verify that the
// current user has permissions for those embedded roles. We assume that
// "create role" is effectively root already and validate only that.
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbRead, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
if err := a.action(apidefaults.Namespace, types.KindRole, types.VerbRead, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
if err := a.action(apidefaults.Namespace, types.KindToken, types.VerbRead, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.createBot(ctx, req)
}
// DeleteBot removes a certificate renewal bot by name.
func (a *ServerWithRoles) DeleteBot(ctx context.Context, botName string) error {
// Requires read + delete on users and roles. We do verify the user and
// role are explicitly associated with a bot before doing anything (must
// match bot-$name and have a matching teleport.dev/bot label set).
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbRead, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
if err := a.action(apidefaults.Namespace, types.KindRole, types.VerbRead, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.deleteBot(ctx, botName)
}
// GetBotUsers fetches all users with bot labels. It does not fetch users with
// secrets.
func (a *ServerWithRoles) GetBotUsers(ctx context.Context) ([]types.User, error) {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.getBotUsers(ctx)
}
func (a *ServerWithRoles) CreateResetPasswordToken(ctx context.Context, req CreateUserTokenRequest) (types.UserToken, error) {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.CreateResetPasswordToken(ctx, req)
}
func (a *ServerWithRoles) GetResetPasswordToken(ctx context.Context, tokenID string) (types.UserToken, error) {
// tokens are their own authz mechanism, no need to double check
return a.authServer.getResetPasswordToken(ctx, tokenID)
}
// ChangeUserAuthentication is implemented by AuthService.ChangeUserAuthentication.
func (a *ServerWithRoles) ChangeUserAuthentication(ctx context.Context, req *proto.ChangeUserAuthenticationRequest) (*proto.ChangeUserAuthenticationResponse, error) {
// Token is it's own authentication, no need to double check.
return a.authServer.ChangeUserAuthentication(ctx, req)
}
// CreateUser inserts a new user entry in a backend.
func (a *ServerWithRoles) CreateUser(ctx context.Context, user types.User) error {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
return a.authServer.CreateUser(ctx, user)
}
// UpdateUser updates an existing user in a backend.
// Captures the auth user who modified the user record.
func (a *ServerWithRoles) UpdateUser(ctx context.Context, user types.User) error {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpdateUser(ctx, user)
}
func (a *ServerWithRoles) UpsertUser(u types.User) error {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
createdBy := u.GetCreatedBy()
if createdBy.IsEmpty() {
u.SetCreatedBy(types.CreatedBy{
User: types.UserRef{Name: a.context.User.GetName()},
})
}
return a.authServer.UpsertUser(u)
}
// CompareAndSwapUser updates an existing user in a backend, but fails if the
// backend's value does not match the expected value.
// Captures the auth user who modified the user record.
func (a *ServerWithRoles) CompareAndSwapUser(ctx context.Context, new, existing types.User) error {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.CompareAndSwapUser(ctx, new, existing)
}
// UpsertOIDCConnector creates or updates an OIDC connector.
func (a *ServerWithRoles) UpsertOIDCConnector(ctx context.Context, connector types.OIDCConnector) error {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
if !modules.GetModules().Features().OIDC {
// TODO(zmb3): ideally we would wrap ErrRequiresEnterprise here, but
// we can't currently propagate wrapped errors across the gRPC boundary,
// and we want tctl to display a clean user-facing message in this case
return trace.AccessDenied("OIDC is only available in Teleport Enterprise")
}
return a.authServer.UpsertOIDCConnector(ctx, connector)
}
func (a *ServerWithRoles) GetOIDCConnector(ctx context.Context, id string, withSecrets bool) (types.OIDCConnector, error) {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbReadNoSecrets); err != nil {
return nil, trace.Wrap(err)
}
if withSecrets {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetOIDCConnector(ctx, id, withSecrets)
}
func (a *ServerWithRoles) GetOIDCConnectors(ctx context.Context, withSecrets bool) ([]types.OIDCConnector, error) {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbReadNoSecrets); err != nil {
return nil, trace.Wrap(err)
}
if withSecrets {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetOIDCConnectors(ctx, withSecrets)
}
func (a *ServerWithRoles) CreateOIDCAuthRequest(ctx context.Context, req types.OIDCAuthRequest) (*types.OIDCAuthRequest, error) {
if err := a.action(apidefaults.Namespace, types.KindOIDCRequest, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
// require additional permissions for executing SSO test flow.
if req.SSOTestFlow {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
}
oidcReq, err := a.authServer.CreateOIDCAuthRequest(ctx, req)
if err != nil {
emitSSOLoginFailureEvent(a.CloseContext(), a.authServer.emitter, events.LoginMethodOIDC, err, req.SSOTestFlow)
return nil, trace.Wrap(err)
}
return oidcReq, nil
}
// GetOIDCAuthRequest returns OIDC auth request if found.
func (a *ServerWithRoles) GetOIDCAuthRequest(ctx context.Context, id string) (*types.OIDCAuthRequest, error) {
if err := a.action(apidefaults.Namespace, types.KindOIDCRequest, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetOIDCAuthRequest(ctx, id)
}
func (a *ServerWithRoles) ValidateOIDCAuthCallback(ctx context.Context, q url.Values) (*OIDCAuthResponse, error) {
// auth callback is it's own authz, no need to check extra permissions
return a.authServer.ValidateOIDCAuthCallback(ctx, q)
}
func (a *ServerWithRoles) DeleteOIDCConnector(ctx context.Context, connectorID string) error {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindOIDC, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteOIDCConnector(ctx, connectorID)
}
// UpsertSAMLConnector creates or updates a SAML connector.
func (a *ServerWithRoles) UpsertSAMLConnector(ctx context.Context, connector types.SAMLConnector) error {
if !modules.GetModules().Features().SAML {
return trace.Wrap(ErrSAMLRequiresEnterprise)
}
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertSAMLConnector(ctx, connector)
}
func (a *ServerWithRoles) GetSAMLConnector(ctx context.Context, id string, withSecrets bool) (types.SAMLConnector, error) {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbReadNoSecrets); err != nil {
return nil, trace.Wrap(err)
}
if withSecrets {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetSAMLConnector(ctx, id, withSecrets)
}
func (a *ServerWithRoles) GetSAMLConnectors(ctx context.Context, withSecrets bool) ([]types.SAMLConnector, error) {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbReadNoSecrets); err != nil {
return nil, trace.Wrap(err)
}
if withSecrets {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetSAMLConnectors(ctx, withSecrets)
}
func (a *ServerWithRoles) CreateSAMLAuthRequest(ctx context.Context, req types.SAMLAuthRequest) (*types.SAMLAuthRequest, error) {
if err := a.action(apidefaults.Namespace, types.KindSAMLRequest, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
// require additional permissions for executing SSO test flow.
if req.SSOTestFlow {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
}
samlReq, err := a.authServer.CreateSAMLAuthRequest(ctx, req)
if err != nil {
emitSSOLoginFailureEvent(a.CloseContext(), a.authServer.emitter, events.LoginMethodSAML, err, req.SSOTestFlow)
return nil, trace.Wrap(err)
}
return samlReq, nil
}
// ValidateSAMLResponse validates SAML auth response.
func (a *ServerWithRoles) ValidateSAMLResponse(ctx context.Context, re string, connectorID string) (*SAMLAuthResponse, error) {
// auth callback is it's own authz, no need to check extra permissions
return a.authServer.ValidateSAMLResponse(ctx, re, connectorID)
}
// GetSAMLAuthRequest returns SAML auth request if found.
func (a *ServerWithRoles) GetSAMLAuthRequest(ctx context.Context, id string) (*types.SAMLAuthRequest, error) {
if err := a.action(apidefaults.Namespace, types.KindSAMLRequest, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetSAMLAuthRequest(ctx, id)
}
// GetSSODiagnosticInfo returns SSO diagnostic info records.
func (a *ServerWithRoles) GetSSODiagnosticInfo(ctx context.Context, authKind string, authRequestID string) (*types.SSODiagnosticInfo, error) {
var resource string
switch authKind {
case types.KindSAML:
resource = types.KindSAMLRequest
case types.KindGithub:
resource = types.KindGithubRequest
case types.KindOIDC:
resource = types.KindOIDCRequest
default:
return nil, trace.BadParameter("unsupported authKind %q", authKind)
}
if err := a.action(apidefaults.Namespace, resource, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetSSODiagnosticInfo(ctx, authKind, authRequestID)
}
// DeleteSAMLConnector deletes a SAML connector by name.
func (a *ServerWithRoles) DeleteSAMLConnector(ctx context.Context, connectorID string) error {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindSAML, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteSAMLConnector(ctx, connectorID)
}
func (a *ServerWithRoles) checkGithubConnector(connector types.GithubConnector) error {
mapping := connector.GetTeamsToLogins()
for _, team := range mapping {
if len(team.KubeUsers) != 0 || len(team.KubeGroups) != 0 {
return trace.BadParameter("since 6.0 teleport uses teams_to_logins to reference a role, use it instead of local kubernetes_users and kubernetes_groups ")
}
for _, localRole := range team.Logins {
_, err := a.GetRole(context.TODO(), localRole)
if err != nil {
if trace.IsNotFound(err) {
return trace.BadParameter("since 6.0 teleport uses teams_to_logins to reference a role, role %q referenced in mapping for organization %q is not found", localRole, team.Organization)
}
return trace.Wrap(err)
}
}
}
return nil
}
// UpsertGithubConnector creates or updates a Github connector.
func (a *ServerWithRoles) UpsertGithubConnector(ctx context.Context, connector types.GithubConnector) error {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
if err := a.checkGithubConnector(connector); err != nil {
return trace.Wrap(err)
}
return a.authServer.upsertGithubConnector(ctx, connector)
}
func (a *ServerWithRoles) GetGithubConnector(ctx context.Context, id string, withSecrets bool) (types.GithubConnector, error) {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbReadNoSecrets); err != nil {
return nil, trace.Wrap(err)
}
if withSecrets {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetGithubConnector(ctx, id, withSecrets)
}
func (a *ServerWithRoles) GetGithubConnectors(ctx context.Context, withSecrets bool) ([]types.GithubConnector, error) {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbReadNoSecrets); err != nil {
return nil, trace.Wrap(err)
}
if withSecrets {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetGithubConnectors(ctx, withSecrets)
}
// DeleteGithubConnector deletes a Github connector by name.
func (a *ServerWithRoles) DeleteGithubConnector(ctx context.Context, connectorID string) error {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.deleteGithubConnector(ctx, connectorID)
}
func (a *ServerWithRoles) CreateGithubAuthRequest(ctx context.Context, req types.GithubAuthRequest) (*types.GithubAuthRequest, error) {
if err := a.action(apidefaults.Namespace, types.KindGithubRequest, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
// require additional permissions for executing SSO test flow.
if req.SSOTestFlow {
if err := a.authConnectorAction(apidefaults.Namespace, types.KindGithub, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
}
githubReq, err := a.authServer.CreateGithubAuthRequest(ctx, req)
if err != nil {
emitSSOLoginFailureEvent(a.authServer.closeCtx, a.authServer.emitter, events.LoginMethodGithub, err, req.SSOTestFlow)
return nil, trace.Wrap(err)
}
return githubReq, nil
}
// GetGithubAuthRequest returns Github auth request if found.
func (a *ServerWithRoles) GetGithubAuthRequest(ctx context.Context, stateToken string) (*types.GithubAuthRequest, error) {
if err := a.action(apidefaults.Namespace, types.KindGithubRequest, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetGithubAuthRequest(ctx, stateToken)
}
func (a *ServerWithRoles) ValidateGithubAuthCallback(ctx context.Context, q url.Values) (*GithubAuthResponse, error) {
return a.authServer.ValidateGithubAuthCallback(ctx, q)
}
// EmitAuditEvent emits a single audit event
func (a *ServerWithRoles) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
if err := a.action(apidefaults.Namespace, types.KindEvent, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
role, ok := a.context.Identity.(authz.BuiltinRole)
if !ok || !role.IsServer() {
return trace.AccessDenied("this request can be only executed by a teleport built-in server")
}
err := events.ValidateServerMetadata(event, role.GetServerID(), a.hasBuiltinRole(types.RoleProxy))
if err != nil {
// TODO: this should be a proper audit event
// notifying about access violation
log.Warningf("Rejecting audit event %v(%q) from %q: %v. The client is attempting to "+
"submit events for an identity other than the one on its x509 certificate.",
event.GetType(), event.GetID(), role.GetServerID(), err)
// this message is sparse on purpose to avoid conveying extra data to an attacker
return trace.AccessDenied("failed to validate event metadata")
}
return a.authServer.emitter.EmitAuditEvent(ctx, event)
}
// CreateAuditStream creates audit event stream
func (a *ServerWithRoles) CreateAuditStream(ctx context.Context, sid session.ID) (apievents.Stream, error) {
if err := a.action(apidefaults.Namespace, types.KindEvent, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
role, ok := a.context.Identity.(authz.BuiltinRole)
if !ok || !role.IsServer() {
return nil, trace.AccessDenied("this request can be only executed by a Teleport server")
}
stream, err := a.authServer.CreateAuditStream(ctx, sid)
if err != nil {
return nil, trace.Wrap(err)
}
return &streamWithRoles{
stream: stream,
a: a,
serverID: role.GetServerID(),
}, nil
}
// ResumeAuditStream resumes the stream that has been created
func (a *ServerWithRoles) ResumeAuditStream(ctx context.Context, sid session.ID, uploadID string) (apievents.Stream, error) {
if err := a.action(apidefaults.Namespace, types.KindEvent, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
role, ok := a.context.Identity.(authz.BuiltinRole)
if !ok || !role.IsServer() {
return nil, trace.AccessDenied("this request can be only executed by a Teleport server")
}
stream, err := a.authServer.ResumeAuditStream(ctx, sid, uploadID)
if err != nil {
return nil, trace.Wrap(err)
}
return &streamWithRoles{
stream: stream,
a: a,
serverID: role.GetServerID(),
}, nil
}
type streamWithRoles struct {
a *ServerWithRoles
serverID string
stream apievents.Stream
}
// Status returns channel receiving updates about stream status
// last event index that was uploaded and upload ID
func (s *streamWithRoles) Status() <-chan apievents.StreamStatus {
return s.stream.Status()
}
// Done returns channel closed when streamer is closed
// should be used to detect sending errors
func (s *streamWithRoles) Done() <-chan struct{} {
return s.stream.Done()
}
// Complete closes the stream and marks it finalized
func (s *streamWithRoles) Complete(ctx context.Context) error {
return s.stream.Complete(ctx)
}
// Close flushes non-uploaded flight stream data without marking
// the stream completed and closes the stream instance
func (s *streamWithRoles) Close(ctx context.Context) error {
return s.stream.Close(ctx)
}
func (s *streamWithRoles) EmitAuditEvent(ctx context.Context, event apievents.AuditEvent) error {
err := events.ValidateServerMetadata(event, s.serverID, s.a.hasBuiltinRole(types.RoleProxy))
if err != nil {
// TODO: this should be a proper audit event
// notifying about access violation
log.Warningf("Rejecting audit event %v from %v: %v. A node is attempting to "+
"submit events for an identity other than the one on its x509 certificate.",
event.GetID(), s.serverID, err)
// this message is sparse on purpose to avoid conveying extra data to an attacker
return trace.AccessDenied("failed to validate event metadata")
}
return s.stream.EmitAuditEvent(ctx, event)
}
func (a *ServerWithRoles) GetSessionChunk(namespace string, sid session.ID, offsetBytes, maxBytes int) ([]byte, error) {
if err := a.actionForKindSession(namespace, sid); err != nil {
return nil, trace.Wrap(err)
}
return a.alog.GetSessionChunk(namespace, sid, offsetBytes, maxBytes)
}
func (a *ServerWithRoles) GetSessionEvents(namespace string, sid session.ID, afterN int) ([]events.EventFields, error) {
if err := a.actionForKindSession(namespace, sid); err != nil {
return nil, trace.Wrap(err)
}
// emit a session recording view event for the audit log
if err := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.SessionRecordingAccess{
Metadata: apievents.Metadata{
Type: events.SessionRecordingAccessEvent,
Code: events.SessionRecordingAccessCode,
},
SessionID: sid.String(),
UserMetadata: a.context.Identity.GetIdentity().GetUserMetadata(),
}); err != nil {
return nil, trace.Wrap(err)
}
return a.alog.GetSessionEvents(namespace, sid, afterN)
}
func (a *ServerWithRoles) findSessionEndEvent(namespace string, sid session.ID) (apievents.AuditEvent, error) {
sessionEvents, _, err := a.alog.SearchSessionEvents(time.Time{}, a.authServer.clock.Now().UTC(),
defaults.EventsIterationLimit, types.EventOrderAscending, "",
&types.WhereExpr{Equals: types.WhereExpr2{
L: &types.WhereExpr{Field: events.SessionEventID},
R: &types.WhereExpr{Literal: sid.String()},
}}, sid.String(),
)
if err != nil {
return nil, trace.Wrap(err)
}
if len(sessionEvents) == 1 {
return sessionEvents[0], nil
}
return nil, trace.NotFound("session end event not found for session ID %q", sid)
}
// GetNamespaces returns a list of namespaces
func (a *ServerWithRoles) GetNamespaces() ([]types.Namespace, error) {
if err := a.action(apidefaults.Namespace, types.KindNamespace, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetNamespaces()
}
// GetNamespace returns namespace by name
func (a *ServerWithRoles) GetNamespace(name string) (*types.Namespace, error) {
if err := a.action(apidefaults.Namespace, types.KindNamespace, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetNamespace(name)
}
// UpsertNamespace upserts namespace
func (a *ServerWithRoles) UpsertNamespace(ns types.Namespace) error {
if err := a.action(apidefaults.Namespace, types.KindNamespace, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertNamespace(ns)
}
// DeleteNamespace deletes namespace by name
func (a *ServerWithRoles) DeleteNamespace(name string) error {
if err := a.action(apidefaults.Namespace, types.KindNamespace, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteNamespace(name)
}
// GetRoles returns a list of roles
func (a *ServerWithRoles) GetRoles(ctx context.Context) ([]types.Role, error) {
if err := a.action(apidefaults.Namespace, types.KindRole, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetRoles(ctx)
}
// CreateRole not implemented: can only be called locally.
func (a *ServerWithRoles) CreateRole(ctx context.Context, role types.Role) error {
return trace.NotImplemented(notImplementedMessage)
}
// UpsertRole creates or updates role.
func (a *ServerWithRoles) UpsertRole(ctx context.Context, role types.Role) error {
if err := a.action(apidefaults.Namespace, types.KindRole, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
// Some options are only available with enterprise subscription
if err := checkRoleFeatureSupport(role); err != nil {
return trace.Wrap(err)
}
// access predicate syntax is not checked as part of normal role validation in order
// to allow the available namespaces to be extended without breaking compatibility with
// older nodes/proxies (which do not need to ever evaluate said predicates).
if err := services.ValidateAccessPredicates(role); err != nil {
return trace.Wrap(err)
}
// check that the given RequireMFAType is supported in this build.
switch role.GetOptions().RequireMFAType {
case types.RequireMFAType_SESSION_AND_HARDWARE_KEY, types.RequireMFAType_HARDWARE_KEY_TOUCH:
if modules.GetModules().BuildType() != modules.BuildEnterprise {
return trace.AccessDenied("Hardware Key support is only available with an enterprise license")
}
}
return a.authServer.UpsertRole(ctx, role)
}
func checkRoleFeatureSupport(role types.Role) error {
features := modules.GetModules().Features()
options := role.GetOptions()
allowReq, allowRev := role.GetAccessRequestConditions(types.Allow), role.GetAccessReviewConditions(types.Allow)
// source IP pinning doesn't have a dedicated feature flag,
// it is available to all enterprise users
if modules.GetModules().BuildType() != modules.BuildEnterprise && role.GetOptions().PinSourceIP {
return trace.AccessDenied("role option pin_source_ip is only available in enterprise subscriptions")
}
switch {
case !features.AccessControls && options.MaxSessions > 0:
return trace.AccessDenied(
"role option max_sessions is only available in enterprise subscriptions")
case !features.AdvancedAccessWorkflows &&
(options.RequestAccess == types.RequestStrategyReason || options.RequestAccess == types.RequestStrategyAlways):
return trace.AccessDenied(
"role option request_access: %v is only available in enterprise subscriptions", options.RequestAccess)
case !features.AdvancedAccessWorkflows && len(allowReq.Thresholds) != 0:
return trace.AccessDenied(
"role field allow.request.thresholds is only available in enterprise subscriptions")
case !features.AdvancedAccessWorkflows && !allowRev.IsZero():
return trace.AccessDenied(
"role field allow.review_requests is only available in enterprise subscriptions")
case modules.GetModules().BuildType() != modules.BuildEnterprise && len(allowReq.SearchAsRoles) != 0:
return trace.AccessDenied(
"role field allow.search_as_roles is only available in enterprise subscriptions")
default:
return nil
}
}
// GetRole returns role by name
func (a *ServerWithRoles) GetRole(ctx context.Context, name string) (types.Role, error) {
// Current-user exception: we always allow users to read roles
// that they hold. This requirement is checked first to avoid
// misleading denial messages in the logs.
if !slices.Contains(a.context.User.GetRoles(), name) {
if err := a.action(apidefaults.Namespace, types.KindRole, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetRole(ctx, name)
}
// DeleteRole deletes role by name
func (a *ServerWithRoles) DeleteRole(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindRole, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
// DELETE IN (7.0)
// It's OK to delete this code alongside migrateOSS code in auth.
// It prevents 6.0 from migrating resources multiple times
// and the role is used for `tctl users add` code too.
if modules.GetModules().BuildType() == modules.BuildOSS && name == teleport.AdminRoleName {
return trace.AccessDenied("can not delete system role %q", name)
}
return a.authServer.DeleteRole(ctx, name)
}
// DeleteClusterName deletes cluster name
func (a *ServerWithRoles) DeleteClusterName() error {
if err := a.action(apidefaults.Namespace, types.KindClusterName, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteClusterName()
}
// GetClusterName gets the name of the cluster.
func (a *ServerWithRoles) GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) {
if err := a.action(apidefaults.Namespace, types.KindClusterName, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetClusterName()
}
// SetClusterName sets the name of the cluster. SetClusterName can only be called once.
func (a *ServerWithRoles) SetClusterName(c types.ClusterName) error {
if err := a.action(apidefaults.Namespace, types.KindClusterName, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.SetClusterName(c)
}
// UpsertClusterName sets the name of the cluster.
func (a *ServerWithRoles) UpsertClusterName(c types.ClusterName) error {
if err := a.action(apidefaults.Namespace, types.KindClusterName, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertClusterName(c)
}
// DeleteStaticTokens deletes static tokens
func (a *ServerWithRoles) DeleteStaticTokens() error {
if err := a.action(apidefaults.Namespace, types.KindStaticTokens, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteStaticTokens()
}
// GetStaticTokens gets the list of static tokens used to provision nodes.
func (a *ServerWithRoles) GetStaticTokens() (types.StaticTokens, error) {
if err := a.action(apidefaults.Namespace, types.KindStaticTokens, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetStaticTokens()
}
// SetStaticTokens sets the list of static tokens used to provision nodes.
func (a *ServerWithRoles) SetStaticTokens(s types.StaticTokens) error {
if err := a.action(apidefaults.Namespace, types.KindStaticTokens, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.SetStaticTokens(s)
}
// GetAuthPreference gets cluster auth preference.
func (a *ServerWithRoles) GetAuthPreference(ctx context.Context) (types.AuthPreference, error) {
if err := a.action(apidefaults.Namespace, types.KindClusterAuthPreference, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetAuthPreference(ctx)
}
func (a *ServerWithRoles) GetUIConfig(ctx context.Context) (types.UIConfig, error) {
if err := a.action(apidefaults.Namespace, types.KindUIConfig, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
cfg, err := a.authServer.GetUIConfig(ctx)
return cfg, trace.Wrap(err)
}
func (a *ServerWithRoles) SetUIConfig(ctx context.Context, uic types.UIConfig) error {
if err := a.action(apidefaults.Namespace, types.KindUIConfig, types.VerbUpdate, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.SetUIConfig(ctx, uic))
}
func (a *ServerWithRoles) DeleteUIConfig(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindUIConfig, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.DeleteUIConfig(ctx))
}
// GetInstaller retrieves an installer script resource
func (a *ServerWithRoles) GetInstaller(ctx context.Context, name string) (types.Installer, error) {
if err := a.action(apidefaults.Namespace, types.KindInstaller, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetInstaller(ctx, name)
}
// GetInstallers gets all the installer resources.
func (a *ServerWithRoles) GetInstallers(ctx context.Context) ([]types.Installer, error) {
if err := a.action(apidefaults.Namespace, types.KindInstaller, types.VerbRead, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetInstallers(ctx)
}
// SetInstaller sets an Installer script resource
func (a *ServerWithRoles) SetInstaller(ctx context.Context, inst types.Installer) error {
if err := a.action(apidefaults.Namespace, types.KindInstaller, types.VerbUpdate, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.SetInstaller(ctx, inst))
}
// DeleteInstaller removes an installer script resource
func (a *ServerWithRoles) DeleteInstaller(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindInstaller, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.DeleteInstaller(ctx, name))
}
// DeleteAllInstallers removes all installer script resources
func (a *ServerWithRoles) DeleteAllInstallers(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindInstaller, types.VerbDelete, types.VerbList); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.DeleteAllInstallers(ctx))
}
// SetAuthPreference sets cluster auth preference.
func (a *ServerWithRoles) SetAuthPreference(ctx context.Context, newAuthPref types.AuthPreference) error {
storedAuthPref, err := a.authServer.GetAuthPreference(ctx)
if err != nil {
return trace.Wrap(err)
}
if err := a.action(apidefaults.Namespace, types.KindClusterAuthPreference, verbsToReplaceResourceWithOrigin(storedAuthPref)...); err != nil {
return trace.Wrap(err)
}
// check that the given RequireMFAType is supported in this build.
switch newAuthPref.GetRequireMFAType() {
case types.RequireMFAType_SESSION_AND_HARDWARE_KEY, types.RequireMFAType_HARDWARE_KEY_TOUCH:
if modules.GetModules().BuildType() != modules.BuildEnterprise {
return trace.AccessDenied("Hardware Key support is only available with an enterprise license")
}
}
if err := dtconfig.ValidateConfigAgainstModules(newAuthPref.GetDeviceTrust()); err != nil {
return trace.Wrap(err)
}
return a.authServer.SetAuthPreference(ctx, newAuthPref)
}
// ResetAuthPreference resets cluster auth preference to defaults.
func (a *ServerWithRoles) ResetAuthPreference(ctx context.Context) error {
storedAuthPref, err := a.authServer.GetAuthPreference(ctx)
if err != nil {
return trace.Wrap(err)
}
if storedAuthPref.Origin() == types.OriginConfigFile {
return trace.BadParameter("config-file configuration cannot be reset")
}
if err := a.action(apidefaults.Namespace, types.KindClusterAuthPreference, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.SetAuthPreference(ctx, types.DefaultAuthPreference())
}
// DeleteAuthPreference not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteAuthPreference(context.Context) error {
return trace.NotImplemented(notImplementedMessage)
}
// GetClusterAuditConfig gets cluster audit configuration.
func (a *ServerWithRoles) GetClusterAuditConfig(ctx context.Context, opts ...services.MarshalOption) (types.ClusterAuditConfig, error) {
if err := a.action(apidefaults.Namespace, types.KindClusterAuditConfig, types.VerbRead); err != nil {
if err2 := a.action(apidefaults.Namespace, types.KindClusterConfig, types.VerbRead); err2 != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetClusterAuditConfig(ctx, opts...)
}
// SetClusterAuditConfig not implemented: can only be called locally.
func (a *ServerWithRoles) SetClusterAuditConfig(ctx context.Context, auditConfig types.ClusterAuditConfig) error {
return trace.NotImplemented(notImplementedMessage)
}
// DeleteClusterAuditConfig not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteClusterAuditConfig(ctx context.Context) error {
return trace.NotImplemented(notImplementedMessage)
}
// GetClusterNetworkingConfig gets cluster networking configuration.
func (a *ServerWithRoles) GetClusterNetworkingConfig(ctx context.Context, opts ...services.MarshalOption) (types.ClusterNetworkingConfig, error) {
if err := a.action(apidefaults.Namespace, types.KindClusterNetworkingConfig, types.VerbRead); err != nil {
if err2 := a.action(apidefaults.Namespace, types.KindClusterConfig, types.VerbRead); err2 != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetClusterNetworkingConfig(ctx, opts...)
}
// SetClusterNetworkingConfig sets cluster networking configuration.
func (a *ServerWithRoles) SetClusterNetworkingConfig(ctx context.Context, newNetConfig types.ClusterNetworkingConfig) error {
storedNetConfig, err := a.authServer.GetClusterNetworkingConfig(ctx)
if err != nil {
return trace.Wrap(err)
}
if err := a.action(apidefaults.Namespace, types.KindClusterNetworkingConfig, verbsToReplaceResourceWithOrigin(storedNetConfig)...); err != nil {
if err2 := a.action(apidefaults.Namespace, types.KindClusterConfig, verbsToReplaceResourceWithOrigin(storedNetConfig)...); err2 != nil {
return trace.Wrap(err)
}
}
tst, err := newNetConfig.GetTunnelStrategyType()
if err != nil {
return trace.Wrap(err)
}
if tst == types.ProxyPeering &&
modules.GetModules().BuildType() != modules.BuildEnterprise {
return trace.AccessDenied("proxy peering is an enterprise-only feature")
}
return a.authServer.SetClusterNetworkingConfig(ctx, newNetConfig)
}
// ResetClusterNetworkingConfig resets cluster networking configuration to defaults.
func (a *ServerWithRoles) ResetClusterNetworkingConfig(ctx context.Context) error {
storedNetConfig, err := a.authServer.GetClusterNetworkingConfig(ctx)
if err != nil {
return trace.Wrap(err)
}
if storedNetConfig.Origin() == types.OriginConfigFile {
return trace.BadParameter("config-file configuration cannot be reset")
}
if err := a.action(apidefaults.Namespace, types.KindClusterNetworkingConfig, types.VerbUpdate); err != nil {
if err2 := a.action(apidefaults.Namespace, types.KindClusterConfig, types.VerbUpdate); err2 != nil {
return trace.Wrap(err)
}
}
return a.authServer.SetClusterNetworkingConfig(ctx, types.DefaultClusterNetworkingConfig())
}
// DeleteClusterNetworkingConfig not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteClusterNetworkingConfig(ctx context.Context) error {
return trace.NotImplemented(notImplementedMessage)
}
// GetSessionRecordingConfig gets session recording configuration.
func (a *ServerWithRoles) GetSessionRecordingConfig(ctx context.Context, opts ...services.MarshalOption) (types.SessionRecordingConfig, error) {
if err := a.action(apidefaults.Namespace, types.KindSessionRecordingConfig, types.VerbRead); err != nil {
if err2 := a.action(apidefaults.Namespace, types.KindClusterConfig, types.VerbRead); err2 != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetSessionRecordingConfig(ctx, opts...)
}
// SetSessionRecordingConfig sets session recording configuration.
func (a *ServerWithRoles) SetSessionRecordingConfig(ctx context.Context, newRecConfig types.SessionRecordingConfig) error {
storedRecConfig, err := a.authServer.GetSessionRecordingConfig(ctx)
if err != nil {
return trace.Wrap(err)
}
if err := a.action(apidefaults.Namespace, types.KindSessionRecordingConfig, verbsToReplaceResourceWithOrigin(storedRecConfig)...); err != nil {
if err2 := a.action(apidefaults.Namespace, types.KindClusterConfig, verbsToReplaceResourceWithOrigin(storedRecConfig)...); err2 != nil {
return trace.Wrap(err)
}
}
return a.authServer.SetSessionRecordingConfig(ctx, newRecConfig)
}
// ResetSessionRecordingConfig resets session recording configuration to defaults.
func (a *ServerWithRoles) ResetSessionRecordingConfig(ctx context.Context) error {
storedRecConfig, err := a.authServer.GetSessionRecordingConfig(ctx)
if err != nil {
return trace.Wrap(err)
}
if storedRecConfig.Origin() == types.OriginConfigFile {
return trace.BadParameter("config-file configuration cannot be reset")
}
if err := a.action(apidefaults.Namespace, types.KindSessionRecordingConfig, types.VerbUpdate); err != nil {
if err2 := a.action(apidefaults.Namespace, types.KindClusterConfig, types.VerbUpdate); err2 != nil {
return trace.Wrap(err)
}
}
return a.authServer.SetSessionRecordingConfig(ctx, types.DefaultSessionRecordingConfig())
}
// DeleteSessionRecordingConfig not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteSessionRecordingConfig(ctx context.Context) error {
return trace.NotImplemented(notImplementedMessage)
}
// DeleteAllTokens not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteAllTokens() error {
return trace.NotImplemented(notImplementedMessage)
}
// DeleteAllCertAuthorities not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteAllCertAuthorities(caType types.CertAuthType) error {
return trace.NotImplemented(notImplementedMessage)
}
// DeleteAllCertNamespaces not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteAllNamespaces() error {
return trace.NotImplemented(notImplementedMessage)
}
// DeleteAllReverseTunnels not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteAllReverseTunnels() error {
return trace.NotImplemented(notImplementedMessage)
}
// DeleteAllRoles not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteAllRoles() error {
return trace.NotImplemented(notImplementedMessage)
}
// DeleteAllUsers not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteAllUsers() error {
return trace.NotImplemented(notImplementedMessage)
}
func (a *ServerWithRoles) GetTrustedClusters(ctx context.Context) ([]types.TrustedCluster, error) {
if err := a.action(apidefaults.Namespace, types.KindTrustedCluster, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetTrustedClusters(ctx)
}
func (a *ServerWithRoles) GetTrustedCluster(ctx context.Context, name string) (types.TrustedCluster, error) {
if err := a.action(apidefaults.Namespace, types.KindTrustedCluster, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetTrustedCluster(ctx, name)
}
// UpsertTrustedCluster creates or updates a trusted cluster.
func (a *ServerWithRoles) UpsertTrustedCluster(ctx context.Context, tc types.TrustedCluster) (types.TrustedCluster, error) {
if err := a.action(apidefaults.Namespace, types.KindTrustedCluster, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.UpsertTrustedCluster(ctx, tc)
}
func (a *ServerWithRoles) ValidateTrustedCluster(ctx context.Context, validateRequest *ValidateTrustedClusterRequest) (*ValidateTrustedClusterResponse, error) {
// the token provides it's own authorization and authentication
return a.authServer.validateTrustedCluster(ctx, validateRequest)
}
// DeleteTrustedCluster deletes a trusted cluster by name.
func (a *ServerWithRoles) DeleteTrustedCluster(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindTrustedCluster, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteTrustedCluster(ctx, name)
}
func (a *ServerWithRoles) UpsertTunnelConnection(conn types.TunnelConnection) error {
if err := a.action(apidefaults.Namespace, types.KindTunnelConnection, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertTunnelConnection(conn)
}
func (a *ServerWithRoles) GetTunnelConnections(clusterName string, opts ...services.MarshalOption) ([]types.TunnelConnection, error) {
if err := a.action(apidefaults.Namespace, types.KindTunnelConnection, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetTunnelConnections(clusterName, opts...)
}
func (a *ServerWithRoles) GetAllTunnelConnections(opts ...services.MarshalOption) ([]types.TunnelConnection, error) {
if err := a.action(apidefaults.Namespace, types.KindTunnelConnection, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetAllTunnelConnections(opts...)
}
func (a *ServerWithRoles) DeleteTunnelConnection(clusterName string, connName string) error {
if err := a.action(apidefaults.Namespace, types.KindTunnelConnection, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteTunnelConnection(clusterName, connName)
}
func (a *ServerWithRoles) DeleteTunnelConnections(clusterName string) error {
if err := a.action(apidefaults.Namespace, types.KindTunnelConnection, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteTunnelConnections(clusterName)
}
func (a *ServerWithRoles) DeleteAllTunnelConnections() error {
if err := a.action(apidefaults.Namespace, types.KindTunnelConnection, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllTunnelConnections()
}
func (a *ServerWithRoles) CreateRemoteCluster(conn types.RemoteCluster) error {
if err := a.action(apidefaults.Namespace, types.KindRemoteCluster, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
return a.authServer.CreateRemoteCluster(conn)
}
func (a *ServerWithRoles) UpdateRemoteCluster(ctx context.Context, rc types.RemoteCluster) error {
if err := a.action(apidefaults.Namespace, types.KindRemoteCluster, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpdateRemoteCluster(ctx, rc)
}
func (a *ServerWithRoles) GetRemoteCluster(clusterName string) (types.RemoteCluster, error) {
if err := a.action(apidefaults.Namespace, types.KindRemoteCluster, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
cluster, err := a.authServer.GetRemoteCluster(clusterName)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.context.Checker.CheckAccessToRemoteCluster(cluster); err != nil {
return nil, utils.OpaqueAccessDenied(err)
}
return cluster, nil
}
func (a *ServerWithRoles) GetRemoteClusters(opts ...services.MarshalOption) ([]types.RemoteCluster, error) {
if err := a.action(apidefaults.Namespace, types.KindRemoteCluster, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
remoteClusters, err := a.authServer.GetRemoteClusters(opts...)
if err != nil {
return nil, trace.Wrap(err)
}
return a.filterRemoteClustersForUser(remoteClusters)
}
// filterRemoteClustersForUser filters remote clusters based on what the current user is authorized to access
func (a *ServerWithRoles) filterRemoteClustersForUser(remoteClusters []types.RemoteCluster) ([]types.RemoteCluster, error) {
filteredClusters := make([]types.RemoteCluster, 0, len(remoteClusters))
for _, rc := range remoteClusters {
if err := a.context.Checker.CheckAccessToRemoteCluster(rc); err != nil {
if trace.IsAccessDenied(err) {
continue
}
return nil, trace.Wrap(err)
}
filteredClusters = append(filteredClusters, rc)
}
return filteredClusters, nil
}
func (a *ServerWithRoles) DeleteRemoteCluster(ctx context.Context, clusterName string) error {
if err := a.action(apidefaults.Namespace, types.KindRemoteCluster, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteRemoteCluster(ctx, clusterName)
}
func (a *ServerWithRoles) DeleteAllRemoteClusters() error {
if err := a.action(apidefaults.Namespace, types.KindRemoteCluster, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllRemoteClusters()
}
// AcquireSemaphore acquires lease with requested resources from semaphore.
func (a *ServerWithRoles) AcquireSemaphore(ctx context.Context, params types.AcquireSemaphoreRequest) (*types.SemaphoreLease, error) {
if err := a.action(apidefaults.Namespace, types.KindSemaphore, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.AcquireSemaphore(ctx, params)
}
// KeepAliveSemaphoreLease updates semaphore lease.
func (a *ServerWithRoles) KeepAliveSemaphoreLease(ctx context.Context, lease types.SemaphoreLease) error {
if err := a.action(apidefaults.Namespace, types.KindSemaphore, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.KeepAliveSemaphoreLease(ctx, lease)
}
// CancelSemaphoreLease cancels semaphore lease early.
func (a *ServerWithRoles) CancelSemaphoreLease(ctx context.Context, lease types.SemaphoreLease) error {
if err := a.action(apidefaults.Namespace, types.KindSemaphore, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.CancelSemaphoreLease(ctx, lease)
}
// GetSemaphores returns a list of all semaphores matching the supplied filter.
func (a *ServerWithRoles) GetSemaphores(ctx context.Context, filter types.SemaphoreFilter) ([]types.Semaphore, error) {
if err := a.action(apidefaults.Namespace, types.KindSemaphore, types.VerbReadNoSecrets, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetSemaphores(ctx, filter)
}
// DeleteSemaphore deletes a semaphore matching the supplied filter.
func (a *ServerWithRoles) DeleteSemaphore(ctx context.Context, filter types.SemaphoreFilter) error {
if err := a.action(apidefaults.Namespace, types.KindSemaphore, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteSemaphore(ctx, filter)
}
// ProcessKubeCSR processes CSR request against Kubernetes CA, returns
// signed certificate if successful.
func (a *ServerWithRoles) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) {
// limits the requests types to proxies to make it harder to break
if !a.hasBuiltinRole(types.RoleProxy) {
return nil, trace.AccessDenied("this request can be only executed by a proxy")
}
return a.authServer.ProcessKubeCSR(req)
}
// GetDatabaseServers returns all registered database servers.
func (a *ServerWithRoles) GetDatabaseServers(ctx context.Context, namespace string, opts ...services.MarshalOption) ([]types.DatabaseServer, error) {
if err := a.action(namespace, types.KindDatabaseServer, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
servers, err := a.authServer.GetDatabaseServers(ctx, namespace, opts...)
if err != nil {
return nil, trace.Wrap(err)
}
// Filter out databases the caller doesn't have access to.
var filtered []types.DatabaseServer
for _, server := range servers {
err := a.checkAccessToDatabase(server.GetDatabase())
if err != nil && !trace.IsAccessDenied(err) {
return nil, trace.Wrap(err)
} else if err == nil {
filtered = append(filtered, server)
}
}
return filtered, nil
}
// UpsertDatabaseServer creates or updates a new database proxy server.
func (a *ServerWithRoles) UpsertDatabaseServer(ctx context.Context, server types.DatabaseServer) (*types.KeepAlive, error) {
if err := a.action(server.GetNamespace(), types.KindDatabaseServer, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.UpsertDatabaseServer(ctx, server)
}
// DeleteDatabaseServer removes the specified database proxy server.
func (a *ServerWithRoles) DeleteDatabaseServer(ctx context.Context, namespace, hostID, name string) error {
if err := a.action(namespace, types.KindDatabaseServer, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteDatabaseServer(ctx, namespace, hostID, name)
}
// DeleteAllDatabaseServers removes all registered database proxy servers.
func (a *ServerWithRoles) DeleteAllDatabaseServers(ctx context.Context, namespace string) error {
if err := a.action(namespace, types.KindDatabaseServer, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllDatabaseServers(ctx, namespace)
}
// UpsertDatabaseService creates or updates a new DatabaseService resource.
func (a *ServerWithRoles) UpsertDatabaseService(ctx context.Context, service types.DatabaseService) (*types.KeepAlive, error) {
if err := a.action(service.GetNamespace(), types.KindDatabaseService, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.UpsertDatabaseService(ctx, service)
}
// DeleteAllDatabaseServices removes all DatabaseService resources.
func (a *ServerWithRoles) DeleteAllDatabaseServices(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindDatabaseService, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllDatabaseServices(ctx)
}
// DeleteDatabaseService removes a specific DatabaseService resource.
func (a *ServerWithRoles) DeleteDatabaseService(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindDatabaseService, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteDatabaseService(ctx, name)
}
// SignDatabaseCSR generates a client certificate used by proxy when talking
// to a remote database service.
func (a *ServerWithRoles) SignDatabaseCSR(ctx context.Context, req *proto.DatabaseCSRRequest) (*proto.DatabaseCSRResponse, error) {
// Only proxy is allowed to request this certificate when proxying
// database client connection to a remote database service.
if !a.hasBuiltinRole(types.RoleProxy) {
return nil, trace.AccessDenied("this request can only be executed by a proxy service")
}
return a.authServer.SignDatabaseCSR(ctx, req)
}
// GenerateDatabaseCert generates a certificate used by a database service
// to authenticate with the database instance.
//
// This certificate can be requested by:
//
// - Cluster administrator using "tctl auth sign --format=db" command locally
// on the auth server to produce a certificate for configuring a self-hosted
// database.
// - Remote user using "tctl auth sign --format=db" command with a remote
// proxy (e.g. Teleport Cloud), as long as they can impersonate system
// role Db.
// - Database service when initiating connection to a database instance to
// produce a client certificate.
// - Proxy service when generating mTLS files to a database
func (a *ServerWithRoles) GenerateDatabaseCert(ctx context.Context, req *proto.DatabaseCertRequest) (*proto.DatabaseCertResponse, error) {
// Check if the User can `create` DatabaseCertificates
err := a.action(apidefaults.Namespace, types.KindDatabaseCertificate, types.VerbCreate)
if err != nil {
if !trace.IsAccessDenied(err) {
return nil, trace.Wrap(err)
}
// Err is access denied, trying the old way
// Check if this is a local cluster admin, or a database service, or a
// user that is allowed to impersonate database service.
if !a.hasBuiltinRole(types.RoleDatabase, types.RoleAdmin) {
if err := a.canImpersonateBuiltinRole(types.RoleDatabase); err != nil {
log.WithError(err).Warnf("User %v tried to generate database certificate but does not have '%s' permission for '%s' kind, nor is allowed to impersonate %q system role",
a.context.User.GetName(), types.VerbCreate, types.KindDatabaseCertificate, types.RoleDatabase)
return nil, trace.AccessDenied(fmt.Sprintf("access denied. User must have '%s' permission for '%s' kind to generate the certificate ", types.VerbCreate, types.KindDatabaseCertificate))
}
}
}
return a.authServer.GenerateDatabaseCert(ctx, req)
}
// GenerateSnowflakeJWT generates JWT in the Snowflake required format.
func (a *ServerWithRoles) GenerateSnowflakeJWT(ctx context.Context, req *proto.SnowflakeJWTRequest) (*proto.SnowflakeJWTResponse, error) {
// Check if this is a local cluster admin, or a database service, or a
// user that is allowed to impersonate database service.
if !a.hasBuiltinRole(types.RoleDatabase, types.RoleAdmin) {
if err := a.canImpersonateBuiltinRole(types.RoleDatabase); err != nil {
log.WithError(err).Warnf("User %v tried to generate database certificate but is not allowed to impersonate %q system role.",
a.context.User.GetName(), types.RoleDatabase)
return nil, trace.AccessDenied(`access denied. The user must be able to impersonate the builtin role and user "Db" in order to generate database certificates, for more info see https://goteleport.com/docs/database-access/reference/cli/#tctl-auth-sign.`)
}
}
return a.authServer.GenerateSnowflakeJWT(ctx, req)
}
// canImpersonateBuiltinRole checks if the current user can impersonate the
// provided system role.
func (a *ServerWithRoles) canImpersonateBuiltinRole(role types.SystemRole) error {
roleCtx, err := authz.NewBuiltinRoleContext(role)
if err != nil {
return trace.Wrap(err)
}
roleSet := services.RoleSet(roleCtx.Checker.Roles())
err = a.context.Checker.CheckImpersonate(a.context.User, roleCtx.User, roleSet.WithoutImplicit())
if err != nil {
return trace.Wrap(err)
}
return nil
}
func (a *ServerWithRoles) checkAccessToApp(app types.Application) error {
return a.context.Checker.CheckAccess(
app,
// MFA is not required for operations on app resources but
// will be enforced at the connection time.
services.AccessState{MFAVerified: true})
}
// GetApplicationServers returns all registered application servers.
func (a *ServerWithRoles) GetApplicationServers(ctx context.Context, namespace string) ([]types.AppServer, error) {
if err := a.action(namespace, types.KindAppServer, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
servers, err := a.authServer.GetApplicationServers(ctx, namespace)
if err != nil {
return nil, trace.Wrap(err)
}
// Filter out apps the caller doesn't have access to.
var filtered []types.AppServer
for _, server := range servers {
err := a.checkAccessToApp(server.GetApp())
if err != nil && !trace.IsAccessDenied(err) {
return nil, trace.Wrap(err)
} else if err == nil {
filtered = append(filtered, server)
}
}
return filtered, nil
}
// UpsertApplicationServer registers an application server.
func (a *ServerWithRoles) UpsertApplicationServer(ctx context.Context, server types.AppServer) (*types.KeepAlive, error) {
if err := a.action(server.GetNamespace(), types.KindAppServer, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.UpsertApplicationServer(ctx, server)
}
// DeleteApplicationServer deletes specified application server.
func (a *ServerWithRoles) DeleteApplicationServer(ctx context.Context, namespace, hostID, name string) error {
if err := a.action(namespace, types.KindAppServer, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteApplicationServer(ctx, namespace, hostID, name)
}
// DeleteAllApplicationServers deletes all registered application servers.
func (a *ServerWithRoles) DeleteAllApplicationServers(ctx context.Context, namespace string) error {
if err := a.action(namespace, types.KindAppServer, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllApplicationServers(ctx, namespace)
}
// GetAppSession gets an application web session.
func (a *ServerWithRoles) GetAppSession(ctx context.Context, req types.GetAppSessionRequest) (types.WebSession, error) {
session, err := a.authServer.GetAppSession(ctx, req)
if err != nil {
return nil, trace.Wrap(err)
}
// Users can only fetch their own app sessions.
if err := a.currentUserAction(session.GetUser()); err != nil {
if err := a.action(apidefaults.Namespace, types.KindWebSession, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return session, nil
}
// GetSnowflakeSession gets a Snowflake web session.
func (a *ServerWithRoles) GetSnowflakeSession(ctx context.Context, req types.GetSnowflakeSessionRequest) (types.WebSession, error) {
session, err := a.authServer.GetSnowflakeSession(ctx, req)
if err != nil {
return nil, trace.Wrap(err)
}
if session.GetSubKind() != types.KindSnowflakeSession {
return nil, trace.AccessDenied("GetSnowflakeSession only allows reading sessions with SubKind Snowflake")
}
// Users can only fetch their own app sessions.
if err := a.currentUserAction(session.GetUser()); err != nil {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return session, nil
}
// GetSAMLIdPSession gets a SAML IdP session.
func (a *ServerWithRoles) GetSAMLIdPSession(ctx context.Context, req types.GetSAMLIdPSessionRequest) (types.WebSession, error) {
session, err := a.authServer.GetSAMLIdPSession(ctx, req)
if err != nil {
return nil, trace.Wrap(err)
}
if session.GetSubKind() != types.KindSAMLIdPSession {
return nil, trace.AccessDenied("GetSAMLIdPSession only allows reading sessions with SubKind SAMLIdpSession")
}
// Users can only fetch their own SAML IdP sessions.
if err := a.currentUserAction(session.GetUser()); err != nil {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return session, nil
}
// GetAppSessions gets all application web sessions.
func (a *ServerWithRoles) GetAppSessions(ctx context.Context) ([]types.WebSession, error) {
if err := a.action(apidefaults.Namespace, types.KindWebSession, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
sessions, err := a.authServer.GetAppSessions(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return sessions, nil
}
// ListAppSessions gets a paginated list of application web sessions.
func (a *ServerWithRoles) ListAppSessions(ctx context.Context, pageSize int, pageToken, user string) ([]types.WebSession, string, error) {
if err := a.action(apidefaults.Namespace, types.KindWebSession, types.VerbList, types.VerbRead); err != nil {
return nil, "", trace.Wrap(err)
}
sessions, nextKey, err := a.authServer.ListAppSessions(ctx, pageSize, pageToken, user)
return sessions, nextKey, trace.Wrap(err)
}
// GetSnowflakeSessions gets all Snowflake web sessions.
func (a *ServerWithRoles) GetSnowflakeSessions(ctx context.Context) ([]types.WebSession, error) {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
sessions, err := a.authServer.GetSnowflakeSessions(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return sessions, nil
}
// ListSAMLIdPSessions gets a paginated list of SAML IdP sessions.
func (a *ServerWithRoles) ListSAMLIdPSessions(ctx context.Context, pageSize int, pageToken, user string) ([]types.WebSession, string, error) {
if err := a.action(apidefaults.Namespace, types.KindWebSession, types.VerbList, types.VerbRead); err != nil {
return nil, "", trace.Wrap(err)
}
sessions, nextKey, err := a.authServer.ListSAMLIdPSessions(ctx, pageSize, pageToken, user)
return sessions, nextKey, trace.Wrap(err)
}
// CreateAppSession creates an application web session. Application web
// sessions represent a browser session the client holds.
func (a *ServerWithRoles) CreateAppSession(ctx context.Context, req types.CreateAppSessionRequest) (types.WebSession, error) {
if err := a.currentUserAction(req.Username); err != nil {
return nil, trace.Wrap(err)
}
session, err := a.authServer.CreateAppSession(ctx, req, a.context.User, a.context.Identity.GetIdentity(), a.context.Checker)
if err != nil {
return nil, trace.Wrap(err)
}
return session, nil
}
// CreateSnowflakeSession creates a Snowflake web session.
func (a *ServerWithRoles) CreateSnowflakeSession(ctx context.Context, req types.CreateSnowflakeSessionRequest) (types.WebSession, error) {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
snowflakeSession, err := a.authServer.CreateSnowflakeSession(ctx, req, a.context.Identity.GetIdentity(), a.context.Checker)
if err != nil {
return nil, trace.Wrap(err)
}
return snowflakeSession, nil
}
// CreateSAMLIdPSession creates a SAML IdP session.
func (a *ServerWithRoles) CreateSAMLIdPSession(ctx context.Context, req types.CreateSAMLIdPSessionRequest) (types.WebSession, error) {
if err := a.action(apidefaults.Namespace, types.KindWebSession, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
samlSession, err := a.authServer.CreateSAMLIdPSession(ctx, req, a.context.Identity.GetIdentity(), a.context.Checker)
if err != nil {
return nil, trace.Wrap(err)
}
return samlSession, nil
}
// UpsertAppSession not implemented: can only be called locally.
func (a *ServerWithRoles) UpsertAppSession(ctx context.Context, session types.WebSession) error {
return trace.NotImplemented(notImplementedMessage)
}
// UpsertSnowflakeSession not implemented: can only be called locally.
func (a *ServerWithRoles) UpsertSnowflakeSession(_ context.Context, _ types.WebSession) error {
return trace.NotImplemented(notImplementedMessage)
}
// UpsertSAMLIdPSession not implemented: can only be called locally.
func (a *ServerWithRoles) UpsertSAMLIdPSession(_ context.Context, _ types.WebSession) error {
return trace.NotImplemented(notImplementedMessage)
}
// DeleteAppSession removes an application web session.
func (a *ServerWithRoles) DeleteAppSession(ctx context.Context, req types.DeleteAppSessionRequest) error {
session, err := a.authServer.GetAppSession(ctx, types.GetAppSessionRequest(req))
if err != nil {
return trace.Wrap(err)
}
// Check if user can delete this web session.
if err := a.canDeleteWebSession(session.GetUser()); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.DeleteAppSession(ctx, req); err != nil {
return trace.Wrap(err)
}
return nil
}
// DeleteSnowflakeSession removes a Snowflake web session.
func (a *ServerWithRoles) DeleteSnowflakeSession(ctx context.Context, req types.DeleteSnowflakeSessionRequest) error {
snowflakeSession, err := a.authServer.GetSnowflakeSession(ctx, types.GetSnowflakeSessionRequest(req))
if err != nil {
return trace.Wrap(err)
}
// Check if user can delete this web session.
if err := a.canDeleteWebSession(snowflakeSession.GetUser()); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.DeleteSnowflakeSession(ctx, req); err != nil {
return trace.Wrap(err)
}
return nil
}
// DeleteSAMLIdPSession removes a SAML IdP session.
func (a *ServerWithRoles) DeleteSAMLIdPSession(ctx context.Context, req types.DeleteSAMLIdPSessionRequest) error {
samlSession, err := a.authServer.GetSAMLIdPSession(ctx, types.GetSAMLIdPSessionRequest(req))
if err != nil {
return trace.Wrap(err)
}
// Check if user can delete this web session.
if err := a.canDeleteWebSession(samlSession.GetUser()); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.DeleteSAMLIdPSession(ctx, req); err != nil {
return trace.Wrap(err)
}
return nil
}
// DeleteAllSnowflakeSessions removes all Snowflake web sessions.
func (a *ServerWithRoles) DeleteAllSnowflakeSessions(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.DeleteAllSnowflakeSessions(ctx); err != nil {
return trace.Wrap(err)
}
return nil
}
// DeleteAllAppSessions removes all application web sessions.
func (a *ServerWithRoles) DeleteAllAppSessions(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindWebSession, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.DeleteAllAppSessions(ctx); err != nil {
return trace.Wrap(err)
}
return nil
}
// DeleteUserAppSessions deletes all user’s application sessions.
func (a *ServerWithRoles) DeleteUserAppSessions(ctx context.Context, req *proto.DeleteUserAppSessionsRequest) error {
// First, check if the current user can delete the request user sessions.
if err := a.canDeleteWebSession(req.Username); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.DeleteUserAppSessions(ctx, req); err != nil {
return trace.Wrap(err)
}
return nil
}
// DeleteAllSAMLIdPSessions removes all SAML IdP sessions.
func (a *ServerWithRoles) DeleteAllSAMLIdPSessions(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindWebSession, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.DeleteAllSAMLIdPSessions(ctx); err != nil {
return trace.Wrap(err)
}
return nil
}
// DeleteUserSAMLIdPSessions deletes all of a user's SAML IdP sessions.
func (a *ServerWithRoles) DeleteUserSAMLIdPSessions(ctx context.Context, username string) error {
// First, check if the current user can delete the request user sessions.
if err := a.canDeleteWebSession(username); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.DeleteUserSAMLIdPSessions(ctx, username); err != nil {
return trace.Wrap(err)
}
return nil
}
// canDeleteWebSession checks if the current user can delete
// WebSessions from the provided `username`.
func (a *ServerWithRoles) canDeleteWebSession(username string) error {
if err := a.currentUserAction(username); err != nil {
if err := a.action(apidefaults.Namespace, types.KindWebSession, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
}
return nil
}
// GenerateAppToken creates a JWT token with application access.
func (a *ServerWithRoles) GenerateAppToken(ctx context.Context, req types.GenerateAppTokenRequest) (string, error) {
if err := a.action(apidefaults.Namespace, types.KindJWT, types.VerbCreate); err != nil {
return "", trace.Wrap(err)
}
session, err := a.authServer.generateAppToken(ctx, req.Username, req.Roles, req.Traits, req.URI, req.Expires)
if err != nil {
return "", trace.Wrap(err)
}
return session, nil
}
func (a *ServerWithRoles) Close() error {
return a.authServer.Close()
}
func (a *ServerWithRoles) checkAccessToKubeCluster(cluster types.KubeCluster) error {
return a.context.Checker.CheckAccess(
cluster,
// MFA is not required for operations on kube clusters resources but
// will be enforced at the connection time.
services.AccessState{MFAVerified: true})
}
// GetKubernetesServers returns all registered kubernetes servers.
func (a *ServerWithRoles) GetKubernetesServers(ctx context.Context) ([]types.KubeServer, error) {
if err := a.action(apidefaults.Namespace, types.KindKubeServer, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
servers, err := a.authServer.GetKubernetesServers(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
// Filter out kube servers the caller doesn't have access to.
var filtered []types.KubeServer
for _, server := range servers {
err := a.checkAccessToKubeCluster(server.GetCluster())
if err != nil && !trace.IsAccessDenied(err) {
return nil, trace.Wrap(err)
} else if err == nil {
filtered = append(filtered, server)
}
}
return filtered, nil
}
// UpsertKubernetesServer creates or updates a Server representing a teleport
// kubernetes server.
func (a *ServerWithRoles) UpsertKubernetesServer(ctx context.Context, s types.KubeServer) (*types.KeepAlive, error) {
if err := a.action(apidefaults.Namespace, types.KindKubeServer, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.UpsertKubernetesServer(ctx, s)
}
// DeleteKubernetesServer deletes specified kubernetes server.
func (a *ServerWithRoles) DeleteKubernetesServer(ctx context.Context, hostID, name string) error {
if err := a.action(apidefaults.Namespace, types.KindKubeServer, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteKubernetesServer(ctx, hostID, name)
}
// DeleteAllKubernetesServers deletes all registered kubernetes servers.
func (a *ServerWithRoles) DeleteAllKubernetesServers(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindKubeServer, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllKubernetesServers(ctx)
}
// GetNetworkRestrictions retrieves all the network restrictions (allow/deny lists).
func (a *ServerWithRoles) GetNetworkRestrictions(ctx context.Context) (types.NetworkRestrictions, error) {
if err := a.action(apidefaults.Namespace, types.KindNetworkRestrictions, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetNetworkRestrictions(ctx)
}
// SetNetworkRestrictions updates the network restrictions.
func (a *ServerWithRoles) SetNetworkRestrictions(ctx context.Context, nr types.NetworkRestrictions) error {
if err := a.action(apidefaults.Namespace, types.KindNetworkRestrictions, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.SetNetworkRestrictions(ctx, nr)
}
// DeleteNetworkRestrictions deletes the network restrictions.
func (a *ServerWithRoles) DeleteNetworkRestrictions(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindNetworkRestrictions, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteNetworkRestrictions(ctx)
}
// GetMFADevices returns a list of MFA devices.
func (a *ServerWithRoles) GetMFADevices(ctx context.Context, req *proto.GetMFADevicesRequest) (*proto.GetMFADevicesResponse, error) {
return a.authServer.GetMFADevices(ctx, req)
}
// TODO(awly): decouple auth.ClientI from auth.ServerWithRoles, they exist on
// opposite sides of the connection.
// AddMFADevice exists to satisfy auth.ClientI but is not implemented here.
// Use auth.GRPCServer.AddMFADevice or client.Client.AddMFADevice instead.
func (a *ServerWithRoles) AddMFADevice(ctx context.Context) (proto.AuthService_AddMFADeviceClient, error) {
return nil, trace.NotImplemented("bug: AddMFADevice must not be called on auth.ServerWithRoles")
}
// DeleteMFADevice exists to satisfy auth.ClientI but is not implemented here.
// Use auth.GRPCServer.DeleteMFADevice or client.Client.DeleteMFADevice instead.
func (a *ServerWithRoles) DeleteMFADevice(ctx context.Context) (proto.AuthService_DeleteMFADeviceClient, error) {
return nil, trace.NotImplemented("bug: DeleteMFADevice must not be called on auth.ServerWithRoles")
}
// AddMFADeviceSync is implemented by AuthService.AddMFADeviceSync.
func (a *ServerWithRoles) AddMFADeviceSync(ctx context.Context, req *proto.AddMFADeviceSyncRequest) (*proto.AddMFADeviceSyncResponse, error) {
// The token provides its own authorization and authentication.
res, err := a.authServer.AddMFADeviceSync(ctx, req)
return res, trace.Wrap(err)
}
// DeleteMFADeviceSync is implemented by AuthService.DeleteMFADeviceSync.
func (a *ServerWithRoles) DeleteMFADeviceSync(ctx context.Context, req *proto.DeleteMFADeviceSyncRequest) error {
// The token provides its own authorization and authentication.
return a.authServer.DeleteMFADeviceSync(ctx, req)
}
// GenerateUserSingleUseCerts exists to satisfy auth.ClientI but is not
// implemented here.
//
// Use auth.GRPCServer.GenerateUserSingleUseCerts or
// client.Client.GenerateUserSingleUseCerts instead.
func (a *ServerWithRoles) GenerateUserSingleUseCerts(ctx context.Context) (proto.AuthService_GenerateUserSingleUseCertsClient, error) {
return nil, trace.NotImplemented("bug: GenerateUserSingleUseCerts must not be called on auth.ServerWithRoles")
}
func (a *ServerWithRoles) IsMFARequired(ctx context.Context, req *proto.IsMFARequiredRequest) (*proto.IsMFARequiredResponse, error) {
if !hasLocalUserRole(a.context) && !hasRemoteUserRole(a.context) {
return nil, trace.AccessDenied("only a user role can call IsMFARequired, got %T", a.context.Checker)
}
return a.authServer.isMFARequired(ctx, a.context.Checker, req)
}
// SearchEvents allows searching audit events with pagination support.
func (a *ServerWithRoles) SearchEvents(fromUTC, toUTC time.Time, namespace string, eventTypes []string, limit int, order types.EventOrder, startKey string) (events []apievents.AuditEvent, lastKey string, err error) {
if err := a.action(apidefaults.Namespace, types.KindEvent, types.VerbList); err != nil {
return nil, "", trace.Wrap(err)
}
events, lastKey, err = a.alog.SearchEvents(fromUTC, toUTC, namespace, eventTypes, limit, order, startKey)
if err != nil {
return nil, "", trace.Wrap(err)
}
return events, lastKey, nil
}
// SearchSessionEvents allows searching session audit events with pagination support.
func (a *ServerWithRoles) SearchSessionEvents(fromUTC, toUTC time.Time, limit int, order types.EventOrder, startKey string, cond *types.WhereExpr, sessionID string) (events []apievents.AuditEvent, lastKey string, err error) {
if cond != nil {
return nil, "", trace.BadParameter("cond is an internal parameter, should not be set by client")
}
cond, err = a.actionForListWithCondition(apidefaults.Namespace, types.KindSession, services.SessionIdentifier)
if err != nil {
return nil, "", trace.Wrap(err)
}
// TODO(codingllama): Refactor cond out of SearchSessionEvents and simplify signature.
events, lastKey, err = a.alog.SearchSessionEvents(fromUTC, toUTC, limit, order, startKey, cond, sessionID)
if err != nil {
return nil, "", trace.Wrap(err)
}
return events, lastKey, nil
}
// GetLock gets a lock by name.
func (a *ServerWithRoles) GetLock(ctx context.Context, name string) (types.Lock, error) {
if err := a.action(apidefaults.Namespace, types.KindLock, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetLock(ctx, name)
}
// GetLocks gets all/in-force locks that match at least one of the targets when specified.
func (a *ServerWithRoles) GetLocks(ctx context.Context, inForceOnly bool, targets ...types.LockTarget) ([]types.Lock, error) {
if err := a.action(apidefaults.Namespace, types.KindLock, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetLocks(ctx, inForceOnly, targets...)
}
// UpsertLock upserts a lock.
func (a *ServerWithRoles) UpsertLock(ctx context.Context, lock types.Lock) error {
if err := a.action(apidefaults.Namespace, types.KindLock, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
if lock.CreatedBy() == "" {
hasAdmin := a.hasBuiltinRole(types.RoleAdmin)
createdBy := string(types.RoleAdmin)
if !hasAdmin {
createdBy = a.context.User.GetName()
}
lock.SetCreatedBy(createdBy)
}
if lock.CreatedAt().IsZero() {
lock.SetCreatedAt(a.authServer.clock.Now().UTC())
}
return a.authServer.UpsertLock(ctx, lock)
}
// DeleteLock deletes a lock.
func (a *ServerWithRoles) DeleteLock(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindLock, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteLock(ctx, name)
}
// DeleteAllLocks not implemented: can only be called locally.
func (a *ServerWithRoles) DeleteAllLocks(context.Context) error {
return trace.NotImplemented(notImplementedMessage)
}
// ReplaceRemoteLocks replaces the set of locks associated with a remote cluster.
func (a *ServerWithRoles) ReplaceRemoteLocks(ctx context.Context, clusterName string, locks []types.Lock) error {
role, ok := a.context.Identity.(authz.RemoteBuiltinRole)
if !a.hasRemoteBuiltinRole(string(types.RoleRemoteProxy)) || !ok || role.ClusterName != clusterName {
return trace.AccessDenied("this request can be only executed by a remote proxy of cluster %q", clusterName)
}
return a.authServer.ReplaceRemoteLocks(ctx, clusterName, locks)
}
// 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 (a *ServerWithRoles) StreamSessionEvents(ctx context.Context, sessionID session.ID, startIndex int64) (chan apievents.AuditEvent, chan error) {
createErrorChannel := func(err error) (chan apievents.AuditEvent, chan error) {
e := make(chan error, 1)
e <- trace.Wrap(err)
return nil, e
}
err := a.serverAction()
isTeleportServer := err == nil
if !isTeleportServer {
if err := a.actionForKindSession(apidefaults.Namespace, sessionID); err != nil {
c, e := make(chan apievents.AuditEvent), make(chan error, 1)
e <- trace.Wrap(err)
return c, e
}
}
// StreamSessionEvents can be called internally, and when that happens we don't want to emit an event.
shouldEmitAuditEvent := !isTeleportServer
if shouldEmitAuditEvent {
if err := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.SessionRecordingAccess{
Metadata: apievents.Metadata{
Type: events.SessionRecordingAccessEvent,
Code: events.SessionRecordingAccessCode,
},
SessionID: sessionID.String(),
UserMetadata: a.context.Identity.GetIdentity().GetUserMetadata(),
}); err != nil {
return createErrorChannel(err)
}
}
return a.alog.StreamSessionEvents(ctx, sessionID, startIndex)
}
// CreateApp creates a new application resource.
func (a *ServerWithRoles) CreateApp(ctx context.Context, app types.Application) error {
if err := a.action(apidefaults.Namespace, types.KindApp, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
// Don't allow users create apps they wouldn't have access to (e.g.
// non-matching labels).
if err := a.checkAccessToApp(app); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.CreateApp(ctx, app))
}
// UpdateApp updates existing application resource.
func (a *ServerWithRoles) UpdateApp(ctx context.Context, app types.Application) error {
if err := a.action(apidefaults.Namespace, types.KindApp, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
// Don't allow users update apps they don't have access to (e.g.
// non-matching labels). Make sure to check existing app too.
existing, err := a.authServer.GetApp(ctx, app.GetName())
if err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToApp(existing); err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToApp(app); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.UpdateApp(ctx, app))
}
// GetApp returns specified application resource.
func (a *ServerWithRoles) GetApp(ctx context.Context, name string) (types.Application, error) {
if err := a.action(apidefaults.Namespace, types.KindApp, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
app, err := a.authServer.GetApp(ctx, name)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.checkAccessToApp(app); err != nil {
return nil, trace.Wrap(err)
}
return app, nil
}
// GetApps returns all application resources.
func (a *ServerWithRoles) GetApps(ctx context.Context) (result []types.Application, err error) {
if err := a.action(apidefaults.Namespace, types.KindApp, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
// Filter out apps user doesn't have access to.
apps, err := a.authServer.GetApps(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
for _, app := range apps {
if err := a.checkAccessToApp(app); err == nil {
result = append(result, app)
}
}
return result, nil
}
// DeleteApp removes the specified application resource.
func (a *ServerWithRoles) DeleteApp(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindApp, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
// Make sure user has access to the application before deleting.
app, err := a.authServer.GetApp(ctx, name)
if err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToApp(app); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.DeleteApp(ctx, name))
}
// DeleteAllApps removes all application resources.
func (a *ServerWithRoles) DeleteAllApps(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindApp, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
// Make sure to only delete apps user has access to.
apps, err := a.authServer.GetApps(ctx)
if err != nil {
return trace.Wrap(err)
}
for _, app := range apps {
if err := a.checkAccessToApp(app); err == nil {
if err := a.authServer.DeleteApp(ctx, app.GetName()); err != nil {
return trace.Wrap(err)
}
}
}
return nil
}
// CreateKubernetesCluster creates a new kubernetes cluster resource.
func (a *ServerWithRoles) CreateKubernetesCluster(ctx context.Context, cluster types.KubeCluster) error {
if err := a.action(apidefaults.Namespace, types.KindKubernetesCluster, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
// Don't allow users create clusters they wouldn't have access to (e.g.
// non-matching labels).
if err := a.checkAccessToKubeCluster(cluster); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.CreateKubernetesCluster(ctx, cluster))
}
// UpdateKubernetesCluster updates existing kubernetes cluster resource.
func (a *ServerWithRoles) UpdateKubernetesCluster(ctx context.Context, cluster types.KubeCluster) error {
if err := a.action(apidefaults.Namespace, types.KindKubernetesCluster, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
// Don't allow users update clusters they don't have access to (e.g.
// non-matching labels). Make sure to check existing cluster too.
existing, err := a.authServer.GetKubernetesCluster(ctx, cluster.GetName())
if err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToKubeCluster(existing); err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToKubeCluster(cluster); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.UpdateKubernetesCluster(ctx, cluster))
}
// GetKubernetesCluster returns specified kubernetes cluster resource.
func (a *ServerWithRoles) GetKubernetesCluster(ctx context.Context, name string) (types.KubeCluster, error) {
if err := a.action(apidefaults.Namespace, types.KindKubernetesCluster, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
kubeCluster, err := a.authServer.GetKubernetesCluster(ctx, name)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.checkAccessToKubeCluster(kubeCluster); err != nil {
return nil, trace.Wrap(err)
}
return kubeCluster, nil
}
// GetKubernetesClusters returns all kubernetes cluster resources.
func (a *ServerWithRoles) GetKubernetesClusters(ctx context.Context) (result []types.KubeCluster, err error) {
if err := a.action(apidefaults.Namespace, types.KindKubernetesCluster, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
// Filter out kube clusters user doesn't have access to.
clusters, err := a.authServer.GetKubernetesClusters(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
for _, cluster := range clusters {
if err := a.checkAccessToKubeCluster(cluster); err == nil {
result = append(result, cluster)
}
}
return result, nil
}
// DeleteKubernetesCluster removes the specified kubernetes cluster resource.
func (a *ServerWithRoles) DeleteKubernetesCluster(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindKubernetesCluster, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
// Make sure user has access to the kubernetes cluster before deleting.
cluster, err := a.authServer.GetKubernetesCluster(ctx, name)
if err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToKubeCluster(cluster); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.DeleteKubernetesCluster(ctx, name))
}
// DeleteAllKubernetesClusters removes all kubernetes cluster resources.
func (a *ServerWithRoles) DeleteAllKubernetesClusters(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindKubernetesCluster, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
// Make sure to only delete kubernetes cluster user has access to.
clusters, err := a.authServer.GetKubernetesClusters(ctx)
if err != nil {
return trace.Wrap(err)
}
for _, cluster := range clusters {
if err := a.checkAccessToKubeCluster(cluster); err == nil {
if err := a.authServer.DeleteKubernetesCluster(ctx, cluster.GetName()); err != nil {
return trace.Wrap(err)
}
}
}
return nil
}
func (a *ServerWithRoles) checkAccessToNode(node types.Server) error {
// For certain built-in roles, continue to allow full access and return
// the full set of nodes to not break existing clusters during migration.
//
// In addition, allow proxy (and remote proxy) to access all nodes for its
// smart resolution address resolution. Once the smart resolution logic is
// moved to the auth server, this logic can be removed.
builtinRole := HasBuiltinRole(a.context, string(types.RoleAdmin)) ||
HasBuiltinRole(a.context, string(types.RoleProxy)) ||
HasRemoteBuiltinRole(a.context, string(types.RoleRemoteProxy))
if builtinRole {
return nil
}
return a.context.Checker.CheckAccess(node,
// MFA is not required for operations on node resources but
// will be enforced at the connection time.
services.AccessState{MFAVerified: true})
}
func (a *ServerWithRoles) checkAccessToDatabase(database types.Database) error {
return a.context.Checker.CheckAccess(database,
// MFA is not required for operations on database resources but
// will be enforced at the connection time.
services.AccessState{MFAVerified: true})
}
// CreateDatabase creates a new database resource.
func (a *ServerWithRoles) CreateDatabase(ctx context.Context, database types.Database) error {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
// Don't allow users create databases they wouldn't have access to (e.g.
// non-matching labels).
if err := a.checkAccessToDatabase(database); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.CreateDatabase(ctx, database))
}
// UpdateDatabase updates existing database resource.
func (a *ServerWithRoles) UpdateDatabase(ctx context.Context, database types.Database) error {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
// Don't allow users update databases they don't have access to (e.g.
// non-matching labels). Make sure to check existing database too.
existing, err := a.authServer.GetDatabase(ctx, database.GetName())
if err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToDatabase(existing); err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToDatabase(database); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.UpdateDatabase(ctx, database))
}
// GetDatabase returns specified database resource.
func (a *ServerWithRoles) GetDatabase(ctx context.Context, name string) (types.Database, error) {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
database, err := a.authServer.GetDatabase(ctx, name)
if err != nil {
return nil, trace.Wrap(err)
}
if err := a.checkAccessToDatabase(database); err != nil {
return nil, trace.Wrap(err)
}
return database, nil
}
// GetDatabases returns all database resources.
func (a *ServerWithRoles) GetDatabases(ctx context.Context) (result []types.Database, err error) {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
// Filter out databases user doesn't have access to.
databases, err := a.authServer.GetDatabases(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
for _, database := range databases {
if err := a.checkAccessToDatabase(database); err == nil {
result = append(result, database)
}
}
return result, nil
}
// DeleteDatabase removes the specified database resource.
func (a *ServerWithRoles) DeleteDatabase(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
// Make sure user has access to the database before deleting.
database, err := a.authServer.GetDatabase(ctx, name)
if err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToDatabase(database); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(a.authServer.DeleteDatabase(ctx, name))
}
// DeleteAllDatabases removes all database resources.
func (a *ServerWithRoles) DeleteAllDatabases(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindDatabase, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
// Make sure to only delete databases user has access to.
databases, err := a.authServer.GetDatabases(ctx)
if err != nil {
return trace.Wrap(err)
}
for _, database := range databases {
if err := a.checkAccessToDatabase(database); err == nil {
if err := a.authServer.DeleteDatabase(ctx, database.GetName()); err != nil {
return trace.Wrap(err)
}
}
}
return nil
}
// GetWindowsDesktopServices returns all registered windows desktop services.
func (a *ServerWithRoles) GetWindowsDesktopServices(ctx context.Context) ([]types.WindowsDesktopService, error) {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktopService, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
services, err := a.authServer.GetWindowsDesktopServices(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
return services, nil
}
// GetWindowsDesktopService returns a registered windows desktop service by name.
func (a *ServerWithRoles) GetWindowsDesktopService(ctx context.Context, name string) (types.WindowsDesktopService, error) {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktopService, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
service, err := a.authServer.GetWindowsDesktopService(ctx, name)
if err != nil {
return nil, trace.Wrap(err)
}
return service, nil
}
// UpsertWindowsDesktopService creates or updates a new windows desktop service.
func (a *ServerWithRoles) UpsertWindowsDesktopService(ctx context.Context, s types.WindowsDesktopService) (*types.KeepAlive, error) {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktopService, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.UpsertWindowsDesktopService(ctx, s)
}
// DeleteWindowsDesktopService removes the specified windows desktop service.
func (a *ServerWithRoles) DeleteWindowsDesktopService(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktopService, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteWindowsDesktopService(ctx, name)
}
// DeleteAllWindowsDesktopServices removes all registered windows desktop services.
func (a *ServerWithRoles) DeleteAllWindowsDesktopServices(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktopService, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllWindowsDesktopServices(ctx)
}
// GetWindowsDesktops returns all registered windows desktop hosts.
func (a *ServerWithRoles) GetWindowsDesktops(ctx context.Context, filter types.WindowsDesktopFilter) ([]types.WindowsDesktop, error) {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktop, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
hosts, err := a.authServer.GetWindowsDesktops(ctx, filter)
if err != nil {
return nil, trace.Wrap(err)
}
filtered, err := a.filterWindowsDesktops(hosts)
if err != nil {
return nil, trace.Wrap(err)
}
return filtered, nil
}
// CreateWindowsDesktop creates a new windows desktop host.
func (a *ServerWithRoles) CreateWindowsDesktop(ctx context.Context, s types.WindowsDesktop) error {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktop, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
return a.authServer.CreateWindowsDesktop(ctx, s)
}
// UpdateWindowsDesktop updates an existing windows desktop host.
func (a *ServerWithRoles) UpdateWindowsDesktop(ctx context.Context, s types.WindowsDesktop) error {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktop, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
existing, err := a.authServer.GetWindowsDesktops(ctx,
types.WindowsDesktopFilter{HostID: s.GetHostID(), Name: s.GetName()})
if err != nil {
return trace.Wrap(err)
}
if len(existing) == 0 {
return trace.NotFound("no windows desktops with HostID %s and Name %s",
s.GetHostID(), s.GetName())
}
if err := a.checkAccessToWindowsDesktop(existing[0]); err != nil {
return trace.Wrap(err)
}
if err := a.checkAccessToWindowsDesktop(s); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpdateWindowsDesktop(ctx, s)
}
// UpsertWindowsDesktop updates a windows desktop resource, creating it if it doesn't exist.
func (a *ServerWithRoles) UpsertWindowsDesktop(ctx context.Context, s types.WindowsDesktop) error {
// Ensure caller has both Create and Update permissions.
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktop, types.VerbCreate, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
if s.GetHostID() == "" {
// dont try to insert desktops with empty hostIDs
return nil
}
// If the desktop exists, check access,
// if it doesn't, continue.
existing, err := a.authServer.GetWindowsDesktops(ctx,
types.WindowsDesktopFilter{HostID: s.GetHostID(), Name: s.GetName()})
if err == nil && len(existing) != 0 {
if err := a.checkAccessToWindowsDesktop(existing[0]); err != nil {
return trace.Wrap(err)
}
} else if err != nil && !trace.IsNotFound(err) {
return trace.Wrap(err)
}
if err := a.checkAccessToWindowsDesktop(s); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpsertWindowsDesktop(ctx, s)
}
// 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 (a *ServerWithRoles) DeleteWindowsDesktop(ctx context.Context, hostID, name string) error {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktop, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
desktop, err := a.authServer.GetWindowsDesktops(ctx,
types.WindowsDesktopFilter{HostID: hostID, Name: name})
if err != nil {
return trace.Wrap(err)
}
if len(desktop) == 0 {
return trace.NotFound("no windows desktops with HostID %s and Name %s",
hostID, name)
}
if err := a.checkAccessToWindowsDesktop(desktop[0]); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteWindowsDesktop(ctx, hostID, name)
}
// DeleteAllWindowsDesktops removes all registered windows desktop hosts.
func (a *ServerWithRoles) DeleteAllWindowsDesktops(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindWindowsDesktop, types.VerbList, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
// Only delete the desktops the user has access to.
desktops, err := a.authServer.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{})
if err != nil {
return trace.Wrap(err)
}
for _, desktop := range desktops {
if err := a.checkAccessToWindowsDesktop(desktop); err == nil {
if err := a.authServer.DeleteWindowsDesktop(ctx, desktop.GetHostID(), desktop.GetName()); err != nil {
return trace.Wrap(err)
}
}
}
return nil
}
func (a *ServerWithRoles) filterWindowsDesktops(desktops []types.WindowsDesktop) ([]types.WindowsDesktop, error) {
// For certain built-in roles allow full access
if a.hasBuiltinRole(types.RoleAdmin, types.RoleProxy, types.RoleWindowsDesktop) {
return desktops, nil
}
filtered := make([]types.WindowsDesktop, 0, len(desktops))
for _, desktop := range desktops {
if err := a.checkAccessToWindowsDesktop(desktop); err == nil {
filtered = append(filtered, desktop)
}
}
return filtered, nil
}
func (a *ServerWithRoles) checkAccessToWindowsDesktop(w types.WindowsDesktop) error {
return a.context.Checker.CheckAccess(w,
// MFA is not required for operations on desktop resources
services.AccessState{MFAVerified: true},
// Note: we don't use the Windows login matcher here, as we won't know what OS user
// the user is trying to log in as until they initiate the connection.
)
}
// GenerateWindowsDesktopCert generates a certificate for Windows RDP or SQL Server
// authentication.
func (a *ServerWithRoles) GenerateWindowsDesktopCert(ctx context.Context, req *proto.WindowsDesktopCertRequest) (*proto.WindowsDesktopCertResponse, error) {
// Only windows_desktop_service should be requesting Windows certificates.
// (We also allow RoleAdmin for tctl auth sign)
if !a.hasBuiltinRole(types.RoleWindowsDesktop, types.RoleAdmin) {
return nil, trace.AccessDenied("access denied")
}
return a.authServer.GenerateWindowsDesktopCert(ctx, req)
}
// GetConnectionDiagnostic returns the connection diagnostic with the matching name
func (a *ServerWithRoles) GetConnectionDiagnostic(ctx context.Context, name string) (types.ConnectionDiagnostic, error) {
if err := a.action(apidefaults.Namespace, types.KindConnectionDiagnostic, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
connectionsDiagnostic, err := a.authServer.GetConnectionDiagnostic(ctx, name)
if err != nil {
return nil, trace.Wrap(err)
}
return connectionsDiagnostic, nil
}
// CreateConnectionDiagnostic creates a new connection diagnostic.
func (a *ServerWithRoles) CreateConnectionDiagnostic(ctx context.Context, connectionDiagnostic types.ConnectionDiagnostic) error {
if err := a.action(apidefaults.Namespace, types.KindConnectionDiagnostic, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.CreateConnectionDiagnostic(ctx, connectionDiagnostic); err != nil {
return trace.Wrap(err)
}
return nil
}
// UpdateConnectionDiagnostic updates a connection diagnostic.
func (a *ServerWithRoles) UpdateConnectionDiagnostic(ctx context.Context, connectionDiagnostic types.ConnectionDiagnostic) error {
if err := a.action(apidefaults.Namespace, types.KindConnectionDiagnostic, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.UpdateConnectionDiagnostic(ctx, connectionDiagnostic); err != nil {
return trace.Wrap(err)
}
return nil
}
// AppendDiagnosticTrace adds a new trace for the given ConnectionDiagnostic.
func (a *ServerWithRoles) AppendDiagnosticTrace(ctx context.Context, name string, t *types.ConnectionDiagnosticTrace) (types.ConnectionDiagnostic, error) {
if err := a.action(apidefaults.Namespace, types.KindConnectionDiagnostic, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.AppendDiagnosticTrace(ctx, name, t)
}
// StartAccountRecovery is implemented by AuthService.StartAccountRecovery.
func (a *ServerWithRoles) StartAccountRecovery(ctx context.Context, req *proto.StartAccountRecoveryRequest) (types.UserToken, error) {
return a.authServer.StartAccountRecovery(ctx, req)
}
// VerifyAccountRecovery is implemented by AuthService.VerifyAccountRecovery.
func (a *ServerWithRoles) VerifyAccountRecovery(ctx context.Context, req *proto.VerifyAccountRecoveryRequest) (types.UserToken, error) {
// The token provides its own authorization and authentication.
return a.authServer.VerifyAccountRecovery(ctx, req)
}
// CompleteAccountRecovery is implemented by AuthService.CompleteAccountRecovery.
func (a *ServerWithRoles) CompleteAccountRecovery(ctx context.Context, req *proto.CompleteAccountRecoveryRequest) error {
// The token provides its own authorization and authentication.
return a.authServer.CompleteAccountRecovery(ctx, req)
}
// CreateAccountRecoveryCodes is implemented by AuthService.CreateAccountRecoveryCodes.
func (a *ServerWithRoles) CreateAccountRecoveryCodes(ctx context.Context, req *proto.CreateAccountRecoveryCodesRequest) (*proto.RecoveryCodes, error) {
return a.authServer.CreateAccountRecoveryCodes(ctx, req)
}
// GetAccountRecoveryToken is implemented by AuthService.GetAccountRecoveryToken.
func (a *ServerWithRoles) GetAccountRecoveryToken(ctx context.Context, req *proto.GetAccountRecoveryTokenRequest) (types.UserToken, error) {
return a.authServer.GetAccountRecoveryToken(ctx, req)
}
// CreateAuthenticateChallenge is implemented by AuthService.CreateAuthenticateChallenge.
func (a *ServerWithRoles) CreateAuthenticateChallenge(ctx context.Context, req *proto.CreateAuthenticateChallengeRequest) (*proto.MFAAuthenticateChallenge, error) {
// No permission check is required b/c this request verifies request by one of the following:
// - username + password, anyone who has user's password can generate a sign request
// - token provide its own auth
// - the user extracted from context can retrieve their own challenges
return a.authServer.CreateAuthenticateChallenge(ctx, req)
}
// CreatePrivilegeToken is implemented by AuthService.CreatePrivilegeToken.
func (a *ServerWithRoles) CreatePrivilegeToken(ctx context.Context, req *proto.CreatePrivilegeTokenRequest) (*types.UserTokenV3, error) {
return a.authServer.CreatePrivilegeToken(ctx, req)
}
// CreateRegisterChallenge is implemented by AuthService.CreateRegisterChallenge.
func (a *ServerWithRoles) CreateRegisterChallenge(ctx context.Context, req *proto.CreateRegisterChallengeRequest) (*proto.MFARegisterChallenge, error) {
// The token provides its own authorization and authentication.
return a.authServer.CreateRegisterChallenge(ctx, req)
}
// GetAccountRecoveryCodes is implemented by AuthService.GetAccountRecoveryCodes.
func (a *ServerWithRoles) GetAccountRecoveryCodes(ctx context.Context, req *proto.GetAccountRecoveryCodesRequest) (*proto.RecoveryCodes, error) {
// User in context can retrieve their own recovery codes.
return a.authServer.GetAccountRecoveryCodes(ctx, req)
}
// GenerateCertAuthorityCRL generates an empty CRL for a CA.
func (a *ServerWithRoles) GenerateCertAuthorityCRL(ctx context.Context, caType types.CertAuthType) ([]byte, error) {
// Only windows_desktop_service should be requesting CRLs
if !a.hasBuiltinRole(types.RoleAdmin, types.RoleWindowsDesktop) {
return nil, trace.AccessDenied("access denied")
}
crl, err := a.authServer.GenerateCertAuthorityCRL(ctx, caType)
if err != nil {
return nil, trace.Wrap(err)
}
return crl, nil
}
// UpdatePresence is coupled to the service layer and must exist here but is never actually called
// since it's handled by the session presence task. This is never valid to call.
func (a *ServerWithRoles) UpdatePresence(ctx context.Context, sessionID, user string) error {
return trace.NotImplemented(notImplementedMessage)
}
// UpdatePresence is coupled to the service layer and must exist here but is never actually called
// since it's handled by the session presence task. This is never valid to call.
func (a *ServerWithRoles) MaintainSessionPresence(ctx context.Context) (proto.AuthService_MaintainSessionPresenceClient, error) {
return nil, trace.NotImplemented(notImplementedMessage)
}
// SubmitUsageEvent submits an external usage event.
func (a *ServerWithRoles) SubmitUsageEvent(ctx context.Context, req *proto.SubmitUsageEventRequest) error {
if err := a.action(apidefaults.Namespace, types.KindUsageEvent, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
if err := a.authServer.SubmitUsageEvent(ctx, req); err != nil {
return trace.Wrap(err)
}
return nil
}
// GetLicense returns the license used to start the auth server
func (a *ServerWithRoles) GetLicense(ctx context.Context) (string, error) {
if err := a.action(apidefaults.Namespace, types.KindLicense, types.VerbRead); err != nil {
return "", trace.Wrap(err)
}
return a.authServer.GetLicense(ctx)
}
// ListReleases return Teleport Enterprise releases
func (a *ServerWithRoles) ListReleases(ctx context.Context) ([]*types.Release, error) {
if err := a.action(apidefaults.Namespace, types.KindDownload, types.VerbList); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.releaseService.ListReleases(ctx)
}
// ListSAMLIdPServiceProviders returns a paginated list of SAML IdP service provider resources.
func (a *ServerWithRoles) ListSAMLIdPServiceProviders(ctx context.Context, pageSize int, nextToken string) ([]types.SAMLIdPServiceProvider, string, error) {
if err := a.action(apidefaults.Namespace, types.KindSAMLIdPServiceProvider, types.VerbList); err != nil {
return nil, "", trace.Wrap(err)
}
return a.authServer.ListSAMLIdPServiceProviders(ctx, pageSize, nextToken)
}
// GetSAMLIdPServiceProvider returns the specified SAML IdP service provider resources.
func (a *ServerWithRoles) GetSAMLIdPServiceProvider(ctx context.Context, name string) (types.SAMLIdPServiceProvider, error) {
if err := a.action(apidefaults.Namespace, types.KindSAMLIdPServiceProvider, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetSAMLIdPServiceProvider(ctx, name)
}
// CreateSAMLIdPServiceProvider creates a new SAML IdP service provider resource.
func (a *ServerWithRoles) CreateSAMLIdPServiceProvider(ctx context.Context, sp types.SAMLIdPServiceProvider) error {
code := events.SAMLIdPServiceProviderCreateFailureCode
var err error
if err = a.action(apidefaults.Namespace, types.KindSAMLIdPServiceProvider, types.VerbCreate); err == nil {
err = a.authServer.CreateSAMLIdPServiceProvider(ctx, sp)
if err == nil {
code = events.SAMLIdPServiceProviderCreateCode
}
}
if emitErr := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.SAMLIdPServiceProviderCreate{
Metadata: apievents.Metadata{
Type: events.SAMLIdPServiceProviderCreateEvent,
Code: code,
},
ResourceMetadata: apievents.ResourceMetadata{
Name: sp.GetName(),
UpdatedBy: authz.ClientUsername(ctx),
},
SAMLIdPServiceProviderMetadata: apievents.SAMLIdPServiceProviderMetadata{
ServiceProviderEntityID: sp.GetEntityID(),
},
}); emitErr != nil {
log.WithError(trace.NewAggregate(emitErr, err)).Warn("Failed to emit SAML IdP service provider created event.")
}
return trace.Wrap(err)
}
// UpdateSAMLIdPServiceProvider updates an existing SAML IdP service provider resource.
func (a *ServerWithRoles) UpdateSAMLIdPServiceProvider(ctx context.Context, sp types.SAMLIdPServiceProvider) error {
code := events.SAMLIdPServiceProviderUpdateFailureCode
var err error
if err = a.action(apidefaults.Namespace, types.KindSAMLIdPServiceProvider, types.VerbUpdate); err == nil {
err = a.authServer.UpdateSAMLIdPServiceProvider(ctx, sp)
if err == nil {
code = events.SAMLIdPServiceProviderUpdateCode
}
}
if emitErr := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.SAMLIdPServiceProviderUpdate{
Metadata: apievents.Metadata{
Type: events.SAMLIdPServiceProviderUpdateEvent,
Code: code,
},
ResourceMetadata: apievents.ResourceMetadata{
Name: sp.GetName(),
UpdatedBy: authz.ClientUsername(ctx),
},
SAMLIdPServiceProviderMetadata: apievents.SAMLIdPServiceProviderMetadata{
ServiceProviderEntityID: sp.GetEntityID(),
},
}); emitErr != nil {
log.WithError(trace.NewAggregate(emitErr, err)).Warn("Failed to emit SAML IdP service provider updated event.")
}
return trace.Wrap(err)
}
// DeleteSAMLIdPServiceProvider removes the specified SAML IdP service provider resource.
func (a *ServerWithRoles) DeleteSAMLIdPServiceProvider(ctx context.Context, name string) error {
var entityID string
code := events.SAMLIdPServiceProviderDeleteFailureCode
var err error
if err = a.action(apidefaults.Namespace, types.KindSAMLIdPServiceProvider, types.VerbDelete); err == nil {
var sp types.SAMLIdPServiceProvider
// Get the service provider so we can emit its entity ID later.
sp, err = a.authServer.GetSAMLIdPServiceProvider(ctx, name)
if err == nil {
name = sp.GetName()
entityID = sp.GetEntityID()
// Delete the actual service provider.
err = a.authServer.DeleteSAMLIdPServiceProvider(ctx, name)
if err == nil {
code = events.SAMLIdPServiceProviderDeleteCode
}
}
}
if emitErr := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.SAMLIdPServiceProviderDelete{
Metadata: apievents.Metadata{
Type: events.SAMLIdPServiceProviderDeleteEvent,
Code: code,
},
ResourceMetadata: apievents.ResourceMetadata{
Name: name,
UpdatedBy: authz.ClientUsername(ctx),
},
SAMLIdPServiceProviderMetadata: apievents.SAMLIdPServiceProviderMetadata{
ServiceProviderEntityID: entityID,
},
}); emitErr != nil {
log.WithError(trace.NewAggregate(emitErr, err)).Warn("Failed to emit SAML IdP service provider deleted event.")
}
return trace.Wrap(err)
}
// DeleteAllSAMLIdPServiceProviders removes all SAML IdP service providers.
func (a *ServerWithRoles) DeleteAllSAMLIdPServiceProviders(ctx context.Context) error {
code := events.SAMLIdPServiceProviderDeleteAllFailureCode
var err error
if err = a.action(apidefaults.Namespace, types.KindSAMLIdPServiceProvider, types.VerbDelete); err == nil {
err = a.authServer.DeleteAllSAMLIdPServiceProviders(ctx)
if err == nil {
code = events.SAMLIdPServiceProviderDeleteAllCode
}
}
if emitErr := a.authServer.emitter.EmitAuditEvent(a.authServer.closeCtx, &apievents.SAMLIdPServiceProviderDeleteAll{
Metadata: apievents.Metadata{
Type: events.SAMLIdPServiceProviderDeleteAllEvent,
Code: code,
},
ResourceMetadata: apievents.ResourceMetadata{
UpdatedBy: authz.ClientUsername(ctx),
},
}); emitErr != nil {
log.WithError(trace.NewAggregate(emitErr, err)).Warn("Failed to emit SAML IdP service provider deleted all event.")
}
return trace.Wrap(err)
}
// ListUserGroups returns a paginated list of user group resources.
func (a *ServerWithRoles) ListUserGroups(ctx context.Context, pageSize int, nextToken string) ([]types.UserGroup, string, error) {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbList); err != nil {
return nil, "", trace.Wrap(err)
}
return a.authServer.ListUserGroups(ctx, pageSize, nextToken)
}
// GetUserGroup returns the specified user group resources.
func (a *ServerWithRoles) GetUserGroup(ctx context.Context, name string) (types.UserGroup, error) {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
return a.authServer.GetUserGroup(ctx, name)
}
// CreateUserGroup creates a new user group resource.
func (a *ServerWithRoles) CreateUserGroup(ctx context.Context, g types.UserGroup) error {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbCreate); err != nil {
return trace.Wrap(err)
}
return a.authServer.CreateUserGroup(ctx, g)
}
// UpdateUserGroup updates an existing user group resource.
func (a *ServerWithRoles) UpdateUserGroup(ctx context.Context, g types.UserGroup) error {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}
return a.authServer.UpdateUserGroup(ctx, g)
}
// DeleteUserGroup removes the specified user group resource.
func (a *ServerWithRoles) DeleteUserGroup(ctx context.Context, name string) error {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteUserGroup(ctx, name)
}
// DeleteAllUserGroups removes all user groups.
func (a *ServerWithRoles) DeleteAllUserGroups(ctx context.Context) error {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbDelete); err != nil {
return trace.Wrap(err)
}
return a.authServer.DeleteAllUserGroups(ctx)
}
// GetHeadlessAuthentication retrieves a headless authentication by id.
func (a *ServerWithRoles) GetHeadlessAuthentication(ctx context.Context, id string) (*types.HeadlessAuthentication, error) {
// TODO (joerger): Add implementation - follow up PR
return nil, trace.NotImplemented("GetHeadlessAuthentication is not implemented")
}
// UpdateHeadlessAuthenticationState updates a headless authentication state.
func (a *ServerWithRoles) UpdateHeadlessAuthenticationState(ctx context.Context, id string, state types.HeadlessAuthenticationState, mfaResp *proto.MFAAuthenticateResponse) error {
// TODO (joerger): Add implementation - follow up PR
return trace.NotImplemented("UpdateHeadlessAuthenticationState is not implemented")
}
// NewAdminAuthServer returns auth server authorized as admin,
// used for auth server cached access
func NewAdminAuthServer(authServer *Server, alog events.AuditLogSessionStreamer) (ClientI, error) {
ctx, err := authz.NewAdminContext()
if err != nil {
return nil, trace.Wrap(err)
}
return &ServerWithRoles{
authServer: authServer,
context: *ctx,
alog: alog,
}, nil
}
func emitSSOLoginFailureEvent(ctx context.Context, emitter apievents.Emitter, method string, err error, testFlow bool) {
code := events.UserSSOLoginFailureCode
if testFlow {
code = events.UserSSOTestFlowLoginFailureCode
}
emitErr := emitter.EmitAuditEvent(ctx, &apievents.UserLogin{
Metadata: apievents.Metadata{
Type: events.UserLoginEvent,
Code: code,
},
Method: method,
Status: apievents.Status{
Success: false,
Error: trace.Unwrap(err).Error(),
UserMessage: err.Error(),
},
})
if emitErr != nil {
log.WithError(err).Warnf("Failed to emit %v login failure event.", method)
}
}
// verbsToReplaceResourceWithOrigin determines the verbs/actions required of a role
// to replace the resource currently stored in the backend.
func verbsToReplaceResourceWithOrigin(stored types.ResourceWithOrigin) []string {
verbs := []string{types.VerbUpdate}
if stored.Origin() == types.OriginConfigFile {
verbs = append(verbs, types.VerbCreate)
}
return verbs
}