Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions lib/auth/grpcserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"net/http"
"net/http/httptest"
"sort"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -66,6 +67,7 @@ import (
"github.com/gravitational/teleport/api/trail"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/autoupdate"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/installers"
"github.com/gravitational/teleport/api/types/vnet"
"github.com/gravitational/teleport/api/utils"
Expand All @@ -82,6 +84,7 @@ import (
"github.com/gravitational/teleport/lib/cryptosuites"
"github.com/gravitational/teleport/lib/defaults"
dtauthz "github.com/gravitational/teleport/lib/devicetrust/authz"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/integrations/awsra/createsession"
iterstream "github.com/gravitational/teleport/lib/itertools/stream"
"github.com/gravitational/teleport/lib/modules"
Expand Down Expand Up @@ -6512,3 +6515,56 @@ func TestRoleVersionV8ToV7Downgrade(t *testing.T) {
})
}
}

func TestSessionRejectedAudit(t *testing.T) {
t.Parallel()
server := newTestTLSServer(t)
ctx := context.Background()
username := "locked-user"

role, _ := types.NewRole("allow-all", types.RoleSpecV6{
Allow: types.RoleConditions{
Namespaces: []string{"*"},
Rules: []types.Rule{{Resources: []string{"*"}, Verbs: []string{"*"}}},
},
})
server.Auth().UpsertRole(ctx, role)

user, _ := types.NewUser(username)
user.SetRoles([]string{"allow-all"})
server.Auth().UpsertUser(ctx, user)

lock, _ := types.NewLock("test-lock", types.LockSpecV2{
Target: types.LockTarget{User: username},
})
server.Auth().UpsertLock(ctx, lock)

client, err := server.NewClient(authtest.TestUser(username))
require.NoError(t, err)
defer client.Close()

_, err = client.GetClusterName(ctx)

require.Error(t, err)
require.True(t,
strings.Contains(err.Error(), "lock") || strings.Contains(err.Error(), "access denied"),
"Expected lock error, got: %v", err)

require.Eventually(t, func() bool {
events, _, _ := server.Auth().SearchEvents(ctx, events.SearchEventsRequest{
From: time.Now().Add(-time.Minute),
To: time.Now().Add(time.Minute),
EventTypes: []string{events.AuthAttemptEvent},
Limit: 100,
})

for _, e := range events {
if attempt, ok := e.(*apievents.AuthAttempt); ok {
if attempt.UserMetadata.User == username && !attempt.Status.Success {
return true
}
}
}
return false
}, 5*time.Second, 100*time.Millisecond, "Expected AuthAttempt event for %v not found", username)
}
87 changes: 87 additions & 0 deletions lib/authz/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import (
"github.com/google/uuid"
"github.com/gravitational/trace"
"github.com/vulcand/predicate/builder"
"google.golang.org/grpc"
"google.golang.org/grpc/peer"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
Expand All @@ -44,6 +46,7 @@ import (
"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/api/utils/keys"
dtauthz "github.com/gravitational/teleport/lib/devicetrust/authz"
"github.com/gravitational/teleport/lib/events"
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/services/readonly"
Expand Down Expand Up @@ -77,13 +80,15 @@ type DeviceAuthorizationOpts struct {

// AuthorizerOpts holds creation options for [NewAuthorizer].
type AuthorizerOpts struct {
TEST string
ClusterName string
AccessPoint AuthorizerAccessPoint
ReadOnlyAccessPoint ReadOnlyAuthorizerAccessPoint
ScopedRoleReader services.ScopedRoleReader
MFAAuthenticator MFAAuthenticator
LockWatcher *services.LockWatcher
Logger *slog.Logger
Emitter apievents.Emitter

// DeviceAuthorization holds Device Trust authorization options.
//
Expand Down Expand Up @@ -129,13 +134,15 @@ func newAuthorizer(opts AuthorizerOpts) (*authorizer, error) {
}

return &authorizer{
TEST: opts.TEST,
clusterName: opts.ClusterName,
accessPoint: opts.AccessPoint,
readOnlyAccessPoint: opts.ReadOnlyAccessPoint,
scopedRoleReader: opts.ScopedRoleReader,
mfaAuthenticator: opts.MFAAuthenticator,
lockWatcher: opts.LockWatcher,
logger: logger,
emitter: opts.Emitter,
disableGlobalDeviceMode: opts.DeviceAuthorization.DisableGlobalMode,
disableRoleDeviceMode: opts.DeviceAuthorization.DisableRoleMode,
}, nil
Expand Down Expand Up @@ -221,13 +228,15 @@ type MFAAuthData struct {

// authorizer creates new local authorizer
type authorizer struct {
TEST string
clusterName string
accessPoint AuthorizerAccessPoint
readOnlyAccessPoint ReadOnlyAuthorizerAccessPoint
scopedRoleReader services.ScopedRoleReader
mfaAuthenticator MFAAuthenticator
lockWatcher *services.LockWatcher
logger *slog.Logger
emitter apievents.Emitter

disableGlobalDeviceMode bool
disableRoleDeviceMode bool
Expand Down Expand Up @@ -430,6 +439,7 @@ func (a *authorizer) Authorize(ctx context.Context) (authCtx *Context, err error
}

if err := CheckIPPinning(ctx, authContext.Identity.GetIdentity(), authContext.Checker.PinSourceIP(), a.logger); err != nil {
a.emitAuthorizeFailure(ctx, authContext.Identity, err)
return nil, trace.Wrap(err)
}

Expand All @@ -441,28 +451,88 @@ func (a *authorizer) Authorize(ctx context.Context) (authCtx *Context, err error
if lockErr := a.lockWatcher.CheckLockInForce(
authContext.Checker.LockingMode(authPref.GetLockingMode()),
authContext.LockTargets()...); lockErr != nil {
a.emitAuthorizeFailure(ctx, authContext.Identity, lockErr)
return nil, trace.Wrap(lockErr)
}

// Enforce required private key policy if set.
if err := a.enforcePrivateKeyPolicy(ctx, authContext, authPref); err != nil {
a.emitAuthorizeFailure(ctx, authContext.Identity, err)
return nil, trace.Wrap(err)
}

// Device Trust: authorize device extensions.
if !a.disableGlobalDeviceMode {
if err := dtauthz.VerifyTLSUser(ctx, authPref.GetDeviceTrust(), authContext.Identity.GetIdentity()); err != nil {
a.emitAuthorizeFailure(ctx, authContext.Identity, err)
return nil, trace.Wrap(err)
}
}

if err := a.checkAdminActionVerification(ctx, authContext); err != nil {
a.emitAuthorizeFailure(ctx, authContext.Identity, err)
return nil, trace.Wrap(err)
}

return authContext, nil
}

func (a *authorizer) emitAuthorizeFailure(ctx context.Context, user IdentityGetter, err error) {
if a.emitter == nil {
return
}
identity := user.GetIdentity()
username := identity.Username

// Filter out failures caused by system components so that we only audit events for real users or bots
if len(identity.SystemRoles) == 0 {
errorMsg := trace.UserMessage(err)
if errorMsg == "" {
errorMsg = err.Error()
}

methodName, _ := grpc.Method(ctx)
if methodName == "" {
// If no gRPC method is found in the context, attempt to get the request method from the context
// (e.g. for kube requests). If that also fails, default to "unknown".
if val := GetRequestMethod(ctx); val != "" {
methodName = val
} else {
methodName = "unknown"
}
}

userMsg := fmt.Sprintf("authorization failed for method %s: %s component %s", methodName, errorMsg, a.TEST)

event := &apievents.AuthAttempt{
Metadata: apievents.Metadata{
Type: events.AuthAttemptEvent,
Code: events.AuthAttemptFailureCode,
},
UserMetadata: apievents.UserMetadata{
User: username,
},
Status: apievents.Status{
Success: false,
Error: errorMsg,
UserMessage: userMsg,
},
ConnectionMetadata: apievents.ConnectionMetadata{},
ServerMetadata: apievents.ServerMetadata{
ServerVersion: teleport.Version,
},
}

if p, _ := peer.FromContext(ctx); p != nil {
event.ConnectionMetadata.RemoteAddr = p.Addr.String()
}

if emitErr := a.emitter.EmitAuditEvent(ctx, event); emitErr != nil {
a.logger.WarnContext(ctx, "Failed to emit detailed audit event", "error", emitErr)
}
}
}

func (a *authorizer) enforcePrivateKeyPolicy(ctx context.Context, authContext *Context, authPref readonly.AuthPreference) error {
switch authContext.Identity.(type) {
case BuiltinRole, RemoteBuiltinRole:
Expand Down Expand Up @@ -1465,11 +1535,28 @@ const (

// contextConn is a connection in the context associated with the request
contextConn contextKey = "teleport-connection"

// contextMethod is used to store the request method/path/action for audit logs.
contextRequestMethod contextKey = "teleport-request-method"
)

// WithDelegator alias for backwards compatibility
var WithDelegator = utils.WithDelegator

// GetRequestMethod returns the request method string from the context.
func GetRequestMethod(ctx context.Context) string {
val, ok := ctx.Value(contextRequestMethod).(string)
if !ok {
return ""
}
return val
}

// WithRequestMethod returns a new context with the request method injected.
func WithRequestMethod(ctx context.Context, method string) context.Context {
return context.WithValue(ctx, contextRequestMethod, method)
}

// ClientUsername returns the username of a remote HTTP client making the call.
// If ctx didn't pass through auth middleware or did not come from an HTTP
// request, teleport.UserSystem is returned.
Expand Down
3 changes: 3 additions & 0 deletions lib/kube/proxy/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,9 @@ func (f *Forwarder) authenticate(req *http.Request) (*authContext, error) {
return nil, trace.AccessDenied("%s", accessDeniedMsg)
}

kubeMethod := fmt.Sprintf("KUBE: %s %s", req.Method, req.URL.Path)
ctx = authz.WithRequestMethod(ctx, kubeMethod)

userContext, err := f.cfg.Authz.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
Expand Down
21 changes: 11 additions & 10 deletions lib/service/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ func (process *TeleportProcess) initDatabaseService() (retErr error) {
databases = append(databases, database)
}

asyncEmitter, err := process.NewAsyncEmitter(conn.Client)
if err != nil {
return trace.Wrap(err)
}
defer func() {
if retErr != nil {
warnOnErr(process.ExitContext(), asyncEmitter.Close(), logger)
}
}()

lockWatcher, err := services.NewLockWatcher(process.ExitContext(), services.LockWatcherConfig{
ResourceWatcherConfig: services.ResourceWatcherConfig{
Component: teleport.ComponentDatabase,
Expand All @@ -106,6 +116,7 @@ func (process *TeleportProcess) initDatabaseService() (retErr error) {
AccessPoint: accessPoint,
LockWatcher: lockWatcher,
Logger: process.logger.With(teleport.ComponentKey, teleport.Component(teleport.ComponentDatabase, process.id)),
Emitter: asyncEmitter,
})
if err != nil {
return trace.Wrap(err)
Expand All @@ -115,16 +126,6 @@ func (process *TeleportProcess) initDatabaseService() (retErr error) {
return trace.Wrap(err)
}

asyncEmitter, err := process.NewAsyncEmitter(conn.Client)
if err != nil {
return trace.Wrap(err)
}
defer func() {
if retErr != nil {
warnOnErr(process.ExitContext(), asyncEmitter.Close(), logger)
}
}()

connLimiter, err := limiter.NewLimiter(process.Config.Databases.Limiter)
if err != nil {
return trace.Wrap(err)
Expand Down
2 changes: 2 additions & 0 deletions lib/service/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,12 @@ func (process *TeleportProcess) initWindowsDesktopServiceRegistered(logger *slog
clusterName := conn.ClusterName()

authorizer, err := authz.NewAuthorizer(authz.AuthorizerOpts{
TEST: "desktop",
ClusterName: clusterName,
AccessPoint: accessPoint,
LockWatcher: lockWatcher,
Logger: process.logger.With(teleport.ComponentKey, teleport.Component(teleport.ComponentWindowsDesktop, process.id)),
Emitter: conn.Client,
DeviceAuthorization: authz.DeviceAuthorizationOpts{
// Ignore the global device_trust.mode toggle, but allow role-based
// settings to be applied.
Expand Down
Loading
Loading