GitHub proxy: emit rbac failure event and add prometheus counters (#51878)

* GitHub proxy: emit rbac failure event and add prometheus counters

* move device metadata to sshca.identity
This commit is contained in:
STeve (Xin) Huang
2025-02-21 16:15:44 +00:00
committed by GitHub
parent 59ded01eb3
commit 4696be3655
5 changed files with 89 additions and 18 deletions
+1 -1
View File
@@ -377,7 +377,7 @@ func (h *AuthHandlers) UserKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*s
UserMetadata: apievents.UserMetadata{
Login: principal,
User: ident.Username,
TrustedDevice: eventDeviceMetadataFromIdentity(ident),
TrustedDevice: ident.GetDeviceMetadata(),
},
ConnectionMetadata: apievents.ConnectionMetadata{
LocalAddr: conn.LocalAddr().String(),
+1 -17
View File
@@ -1066,22 +1066,6 @@ func (c *ServerContext) ExecCommand() (*ExecCommand, error) {
}, nil
}
func eventDeviceMetadataFromIdentity(ident *sshca.Identity) *apievents.DeviceMetadata {
if ident == nil {
return nil
}
if ident.DeviceID == "" && ident.DeviceAssetTag == "" && ident.DeviceCredentialID == "" {
return nil
}
return &apievents.DeviceMetadata{
DeviceId: ident.DeviceID,
AssetTag: ident.DeviceAssetTag,
CredentialId: ident.DeviceCredentialID,
}
}
func (id *IdentityContext) GetUserMetadata() apievents.UserMetadata {
userKind := apievents.UserKind_USER_KIND_HUMAN
if id.BotName != "" {
@@ -1093,7 +1077,7 @@ func (id *IdentityContext) GetUserMetadata() apievents.UserMetadata {
User: id.TeleportUser,
Impersonator: id.Impersonator,
AccessRequests: id.ActiveRequests,
TrustedDevice: eventDeviceMetadataFromIdentity(id.UnmappedIdentity),
TrustedDevice: id.UnmappedIdentity.GetDeviceMetadata(),
UserKind: userKind,
BotName: id.BotName,
BotInstanceID: id.BotInstanceID,
+62
View File
@@ -28,6 +28,7 @@ import (
"github.com/google/uuid"
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
"github.com/prometheus/client_golang/prometheus"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport"
@@ -37,6 +38,7 @@ import (
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/bpf"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/observability/metrics"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/srv"
@@ -46,6 +48,38 @@ import (
logutils "github.com/gravitational/teleport/lib/utils/log"
)
var (
execSessionCounter = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: teleport.MetricNamespace,
Subsystem: teleport.ComponentGit,
Name: "exec_sessions_total",
})
execFailureCounter = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: teleport.MetricNamespace,
Subsystem: teleport.ComponentGit,
Name: "exec_failures_total",
})
userKeyAuthFailureCounter = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: teleport.MetricNamespace,
Subsystem: teleport.ComponentGit,
Name: "userkeyauth_failures_total",
})
rbacFailureCounter = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: teleport.MetricNamespace,
Subsystem: teleport.ComponentGit,
Name: "rbac_failures_total",
})
forwardServerPrometheusCollectors = []prometheus.Collector{
execSessionCounter,
userKeyAuthFailureCounter,
rbacFailureCounter,
}
)
// ForwardServerConfig is the configuration for the ForwardServer.
type ForwardServerConfig struct {
// ParentContext is a parent context, used to signal global
@@ -172,6 +206,10 @@ func NewForwardServer(cfg *ForwardServerConfig) (*ForwardServer, error) {
return nil, trace.Wrap(err)
}
if err := metrics.RegisterPrometheusCollectors(forwardServerPrometheusCollectors...); err != nil {
return nil, trace.Wrap(err)
}
serverConn, clientConn, err := utils.DualPipeNetConn(cfg.SrcAddr, cfg.DstAddr)
if err != nil {
return nil, trace.Wrap(err)
@@ -282,6 +320,7 @@ func (s *ForwardServer) userKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*
// Use auth.UserKeyAuth to verify user cert is signed by UserCA.
permissions, err := s.auth.UserKeyAuth(conn, key)
if err != nil {
userKeyAuthFailureCounter.Inc()
return nil, trace.Wrap(err)
}
@@ -295,6 +334,26 @@ func (s *ForwardServer) userKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*
"fingerprint", sshutils.Fingerprint(key),
"user", cert.KeyId,
)
rbacFailureCounter.Inc()
s.emitEvent(&apievents.AuthAttempt{
Metadata: apievents.Metadata{
Type: events.AuthAttemptEvent,
Code: events.AuthAttemptFailureCode,
},
UserMetadata: apievents.UserMetadata{
Login: gitUser,
User: ident.Username,
TrustedDevice: ident.GetDeviceMetadata(),
},
ConnectionMetadata: apievents.ConnectionMetadata{
LocalAddr: conn.LocalAddr().String(),
RemoteAddr: conn.RemoteAddr().String(),
},
Status: apievents.Status{
Success: false,
Error: err.Error(),
},
})
return nil, trace.Wrap(err)
}
return permissions, nil
@@ -445,7 +504,10 @@ func (s *ForwardServer) handleExec(ctx context.Context, sctx *sessionContext, re
var r sshutils.ExecReq
defer func() {
if err != nil {
execFailureCounter.Inc()
s.emitEvent(s.makeGitCommandEvent(sctx, r.Command, err))
} else {
execSessionCounter.Inc()
}
}()
+8
View File
@@ -136,6 +136,14 @@ func TestForwardServer(t *testing.T) {
clientLogin: "git",
verifyRemoteHost: ssh.InsecureIgnoreHostKey(),
wantNewClientError: true,
verifyEvent: func(t *testing.T, event apievents.AuditEvent) {
authFailureEvent, ok := event.(*apievents.AuthAttempt)
require.True(t, ok)
assert.Equal(t, libevents.AuthAttemptEvent, authFailureEvent.Metadata.Type)
assert.Equal(t, libevents.AuthAttemptFailureCode, authFailureEvent.Metadata.Code)
assert.Equal(t, "alice", authFailureEvent.User)
assert.Contains(t, authFailureEvent.Error, "access denied")
},
},
{
name: "failed client login check",
+17
View File
@@ -32,6 +32,7 @@ import (
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/wrappers"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/lib/utils"
@@ -306,6 +307,22 @@ func (i *Identity) Encode(certFormat string) (*ssh.Certificate, error) {
return cert, nil
}
// GetDeviceMetadata returns information about user's trusted device.
func (i *Identity) GetDeviceMetadata() *apievents.DeviceMetadata {
if i == nil {
return nil
}
if i.DeviceID == "" && i.DeviceAssetTag == "" && i.DeviceCredentialID == "" {
return nil
}
return &apievents.DeviceMetadata{
DeviceId: i.DeviceID,
AssetTag: i.DeviceAssetTag,
CredentialId: i.DeviceCredentialID,
}
}
// DecodeIdentity decodes an ssh certificate into an identity.
func DecodeIdentity(cert *ssh.Certificate) (*Identity, error) {
ident := &Identity{