Skip to content
Merged
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
14 changes: 14 additions & 0 deletions api/proto/teleport/legacy/types/events/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3570,6 +3570,18 @@ message PostgresFunctionCall {
repeated string FunctionArgs = 6 [(gogoproto.jsontag) = "function_args,omitempty"];
}

// WindowsCertificateMetadata contains metadata about certificates
// issued for Windows environments.
message WindowsCertificateMetadata {
string Subject = 1 [(gogoproto.jsontag) = "subject"];
string SerialNumber = 2 [(gogoproto.jsontag) = "serial_number"];
string UPN = 3 [(gogoproto.jsontag) = "upn"];
repeated string CRLDistributionPoints = 4 [(gogoproto.jsontag) = "crl_distribution_points"];
int32 KeyUsage = 5 [(gogoproto.jsontag) = "key_usage"];
repeated int32 ExtendedKeyUsage = 6 [(gogoproto.jsontag) = "extended_key_usage"];
repeated string EnhancedKeyUsage = 7 [(gogoproto.jsontag) = "enhanced_key_usage"];
}

// WindowsDesktopSessionStart is emitted when a user connects to a desktop.
message WindowsDesktopSessionStart {
// Metadata is common event metadata.
Expand Down Expand Up @@ -3620,6 +3632,8 @@ message WindowsDesktopSessionStart {
// NLA indicates whether Teleport performed Network Level Authentication (NLA)
// when initiating this session.
bool NLA = 13 [(gogoproto.jsontag) = "nla"];

WindowsCertificateMetadata CertMetadata = 14 [(gogoproto.jsontag) = "certificate"];
}

// DatabaseSessionEnd is emitted when a user ends the database session.
Expand Down
3,435 changes: 1,996 additions & 1,439 deletions api/types/events/events.pb.go

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion lib/events/events_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,8 @@ func TestJSON(t *testing.T) {
},
{
name: "desktop session start",
json: `{"uid":"cd06365f-3cef-4b21-809a-4af9502c11a1","user":"foo","impersonator":"bar","login":"Administrator","success":true,"proto":"tdp","sid":"test-session","addr.local":"192.168.1.100:39887","addr.remote":"[::1]:34902","with_mfa":"mfa-device","code":"TDP00I","event":"windows.desktop.session.start","time":"2020-04-23T18:22:35.35Z","ei":4,"cluster_name":"test-cluster","windows_user":"Administrator","windows_domain":"test.example.com","desktop_name":"test-desktop","desktop_addr":"[::1]:34902","windows_desktop_service":"00baaef5-ff1e-4222-85a5-c7cb0cd8e7b8","allow_user_creation":false,"nla":true,"desktop_labels":{"env":"production"}}`,
json: `{"uid":"cd06365f-3cef-4b21-809a-4af9502c11a1","user":"foo","impersonator":"bar","login":"Administrator","success":true,"proto":"tdp","sid":"test-session","addr.local":"192.168.1.100:39887","addr.remote":"[::1]:34902","with_mfa":"mfa-device","code":"TDP00I","event":"windows.desktop.session.start","time":"2020-04-23T18:22:35.35Z","ei":4,"cluster_name":"test-cluster","windows_user":"Administrator","windows_domain":"test.example.com","desktop_name":"test-desktop","desktop_addr":"[::1]:34902","windows_desktop_service":"00baaef5-ff1e-4222-85a5-c7cb0cd8e7b8","allow_user_creation":false,"nla":true,"desktop_labels":{"env":"production"},
"certificate": { "crl_distribution_points": [ "ldap:///CN=ABC123_exampple,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint" ], "enhanced_key_usage": [ "1.3.6.1.5.5.7.3.2", "1.3.6.1.4.1.311.20.2.2" ], "extended_key_usage": [ 2 ], "key_usage": 1, "serial_number": "325419247945947193356212349496095884841", "subject": "CN=Administrator", "upn": "Administrator@example.com" } }`,
event: apievents.WindowsDesktopSessionStart{
Metadata: apievents.Metadata{
Index: 4,
Expand All @@ -758,6 +759,15 @@ func TestJSON(t *testing.T) {
RemoteAddr: "[::1]:34902",
Protocol: EventProtocolTDP,
},
CertMetadata: &apievents.WindowsCertificateMetadata{
Subject: "CN=Administrator",
UPN: "Administrator@example.com",
CRLDistributionPoints: []string{"ldap:///CN=ABC123_exampple,CN=Teleport,CN=CDP,CN=Public Key Services,CN=Services,CN=Configuration,DC=example,DC=com?certificateRevocationList?base?objectClass=cRLDistributionPoint"},
KeyUsage: 1,
EnhancedKeyUsage: []string{"1.3.6.1.5.5.7.3.2", "1.3.6.1.4.1.311.20.2.2"},
ExtendedKeyUsage: []int32{2},
SerialNumber: "325419247945947193356212349496095884841",
},
Status: apievents.Status{
Success: true,
},
Expand Down
1 change: 1 addition & 0 deletions lib/srv/desktop/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ func (d *desktopSessionAuditor) makeSessionStart(err error) *events.WindowsDeskt
ClusterName: d.clusterName,
Time: d.startTime,
},
CertMetadata: new(events.WindowsCertificateMetadata),
UserMetadata: userMetadata,
SessionMetadata: d.getSessionMetadata(),
ConnectionMetadata: d.getConnectionMetadata(),
Expand Down
1 change: 1 addition & 0 deletions lib/srv/desktop/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func TestSessionStartEvent(t *testing.T) {
RemoteAddr: testDesktop.GetAddr(),
Protocol: libevents.EventProtocolTDP,
},
CertMetadata: new(events.WindowsCertificateMetadata),
Status: events.Status{
Success: true,
},
Expand Down
23 changes: 9 additions & 14 deletions lib/srv/desktop/rdp/rdpclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ func New(cfg Config) (*Client, error) {
return c, nil
}

// Run starts the rdp client and blocks until the client disconnects,
// then ensures the cleanup is run.
func (c *Client) Run(ctx context.Context) error {
// Run starts the RDP client, using the provided user certificate and private key.
// It blocks until the client disconnects, then ensures the cleanup is run.
func (c *Client) Run(ctx context.Context, certDER, keyDER []byte) error {
// Create a handle to the client to pass to Rust.
// The handle is used to call back into this Client from Rust.
// Since the handle is created and deleted here, methods which
Expand All @@ -213,7 +213,7 @@ func (c *Client) Run(ctx context.Context) error {
rustRDPReturnCh := make(chan error, 1)
// Kick off rust RDP loop goroutine
go func() {
rustRDPReturnCh <- c.startRustRDP(ctx)
rustRDPReturnCh <- c.startRustRDP(ctx, certDER, keyDER)
}()

select {
Expand Down Expand Up @@ -316,15 +316,10 @@ func (c *Client) sendTDPAlert(message string, severity tdp.Severity) error {
return c.cfg.Conn.WriteMessage(tdp.Alert{Message: message, Severity: severity})
}

func (c *Client) startRustRDP(ctx context.Context) error {
func (c *Client) startRustRDP(ctx context.Context, certDER, keyDER []byte) error {
c.cfg.Logger.InfoContext(ctx, "Rust RDP loop starting")
defer c.cfg.Logger.InfoContext(ctx, "Rust RDP loop finished")

userCertDER, userKeyDER, err := c.cfg.GenerateUserCert(ctx, c.username, c.cfg.CertTTL)
if err != nil {
return trace.Wrap(err)
}

// [username] need only be valid for the duration of
// C.client_run. It is copied on the Rust side and
// thus can be freed here.
Expand All @@ -349,14 +344,14 @@ func (c *Client) startRustRDP(ctx context.Context) error {
computerName := C.CString(c.cfg.ComputerName)
defer C.free(unsafe.Pointer(computerName))

cert_der, err := utils.UnsafeSliceData(userCertDER)
cert_der, err := utils.UnsafeSliceData(certDER)
if err != nil {
return trace.Wrap(err)
} else if cert_der == nil {
return trace.BadParameter("user cert was nil")
}

key_der, err := utils.UnsafeSliceData(userKeyDER)
key_der, err := utils.UnsafeSliceData(keyDER)
if err != nil {
return trace.Wrap(err)
} else if key_der == nil {
Expand Down Expand Up @@ -386,10 +381,10 @@ func (c *Client) startRustRDP(ctx context.Context) error {
go_computer_name: computerName,
go_kdc_addr: kdcAddr,
// cert length and bytes.
cert_der_len: C.uint32_t(len(userCertDER)),
cert_der_len: C.uint32_t(len(certDER)),
cert_der: (*C.uint8_t)(cert_der),
// key length and bytes.
key_der_len: C.uint32_t(len(userKeyDER)),
key_der_len: C.uint32_t(len(keyDER)),
key_der: (*C.uint8_t)(key_der),
screen_width: C.uint16_t(c.requestedWidth),
screen_height: C.uint16_t(c.requestedHeight),
Expand Down
11 changes: 0 additions & 11 deletions lib/srv/desktop/rdp/rdpclient/client_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"context"
"image/png"
"log/slog"
"time"

"github.com/gravitational/trace"

Expand All @@ -46,10 +45,6 @@ type Config struct {
LicenseStore LicenseStore
HostID string

// UserCertGenerator generates user certificates for RDP authentication.
GenerateUserCert GenerateUserCertFn
CertTTL time.Duration

// AuthorizeFn is called to authorize a user connecting to a Windows desktop.
AuthorizeFn func(login string) error

Expand Down Expand Up @@ -97,17 +92,11 @@ type Config struct {
AD bool
}

// GenerateUserCertFn generates user certificates for RDP authentication.
type GenerateUserCertFn func(ctx context.Context, username string, ttl time.Duration) (certDER, keyDER []byte, err error)

//nolint:unused // used in client.go that is behind desktop_access_rdp build flag
func (c *Config) checkAndSetDefaults() error {
if c.Addr == "" {
return trace.BadParameter("missing Addr in rdpclient.Config")
}
if c.GenerateUserCert == nil {
return trace.BadParameter("missing GenerateUserCert in rdpclient.Config")
}
if c.Conn == nil {
return trace.BadParameter("missing Conn in rdpclient.Config")
}
Expand Down
6 changes: 3 additions & 3 deletions lib/srv/desktop/rdp/rdpclient/client_nop.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ func New(cfg Config) (*Client, error) {
return nil, errors.New("the real rdpclient.Client implementation was not included in this build")
}

// Run starts the rdp client and blocks until the client disconnects,
// then runs the cleanup.
func (c *Client) Run(ctx context.Context) error {
// Run starts the RDP client, using the provided user certificate and private key.
// It blocks until the client disconnects, then ensures the cleanup is run.
func (c *Client) Run(ctx context.Context, certDER, keyDER []byte) error {
return errors.New("the real rdpclient.Client implementation was not included in this build")
}

Expand Down
15 changes: 4 additions & 11 deletions lib/srv/desktop/rdp/rdpclient/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ package rdpclient

import (
"bytes"
"context"
"log/slog"
"testing"
"time"

"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -109,14 +107,9 @@ func TestClientNew_KeyboardLayout(t *testing.T) {

func createConfig(conn *tdp.Conn) Config {
return Config{
Addr: "example.com",
GenerateUserCert: func(_ context.Context, _ string, _ time.Duration) ([]byte, []byte, error) {
return nil, nil, nil
},
AuthorizeFn: func(login string) error {
return nil
},
Conn: conn,
Logger: slog.Default(),
Addr: "example.com",
AuthorizeFn: func(login string) error { return nil },
Conn: conn,
Logger: slog.Default(),
}
}
59 changes: 51 additions & 8 deletions lib/srv/desktop/windows_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/asn1"
"errors"
"fmt"
"log/slog"
Expand Down Expand Up @@ -56,6 +57,7 @@ import (
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
logutils "github.com/gravitational/teleport/lib/utils/log"
"github.com/gravitational/teleport/lib/utils/slices"
"github.com/gravitational/teleport/lib/winpki"
)

Expand Down Expand Up @@ -784,13 +786,9 @@ func (s *WindowsService) connectRDP(ctx context.Context, log *slog.Logger, tdpCo

//nolint:staticcheck // SA4023. False positive, depends on build tags.
rdpc, err := rdpclient.New(rdpclient.Config{
LicenseStore: s.cfg.LicenseStore,
HostID: s.cfg.Heartbeat.HostUUID,
Logger: log,
GenerateUserCert: func(ctx context.Context, username string, ttl time.Duration) (certDER, keyDER []byte, err error) {
return s.generateUserCert(ctx, username, ttl, desktop, createUsers, groups)
},
CertTTL: windowsUserCertTTL,
LicenseStore: s.cfg.LicenseStore,
HostID: s.cfg.Heartbeat.HostUUID,
Logger: log,
Addr: addr.String(),
ComputerName: computerName,
KDCAddr: kdcAddr,
Expand All @@ -811,6 +809,7 @@ func (s *WindowsService) connectRDP(ctx context.Context, log *slog.Logger, tdpCo
windowsUser = rdpc.GetClientUsername()
audit.windowsUser = windowsUser
}

//nolint:staticcheck // SA4023. False positive, depends on build tags.
if err != nil {
startEvent := audit.makeSessionStart(err)
Expand All @@ -819,6 +818,12 @@ func (s *WindowsService) connectRDP(ctx context.Context, log *slog.Logger, tdpCo
return trace.Wrap(err)
}

// Generate client certificates to be used for the RDP connection.
certDER, keyDER, err := s.generateUserCert(ctx, windowsUser, windowsUserCertTTL, desktop, createUsers, groups)
if err != nil {
return trace.Wrap(err, "could not generate client certificates for RDP")
}

if err := s.trackSession(ctx, &identity, windowsUser, string(sessionID), desktop); err != nil {
return trace.Wrap(err)
}
Expand Down Expand Up @@ -860,10 +865,18 @@ func (s *WindowsService) connectRDP(ctx context.Context, log *slog.Logger, tdpCo
startEvent := audit.makeSessionStart(nil)
startEvent.AllowUserCreation = createUsers

// Parse some information about the cert, which we'll use in order to enhance
// the session start event.
userCert, err := x509.ParseCertificate(certDER)
if err != nil {
return trace.Wrap(err, "the user certificate for RDP is invalid")
}
populateCertMetadata(startEvent.CertMetadata, userCert)

s.record(ctx, recorder, startEvent)
s.emit(ctx, startEvent)

err = rdpc.Run(ctx)
err = rdpc.Run(ctx, certDER, keyDER)

// ctx may have been canceled, so emit with a separate context
endEvent := audit.makeSessionEnd(recordSession)
Expand All @@ -873,6 +886,36 @@ func (s *WindowsService) connectRDP(ctx context.Context, log *slog.Logger, tdpCo
return trace.Wrap(err)
}

func populateCertMetadata(metadata *events.WindowsCertificateMetadata, cert *x509.Certificate) {
var enhancedKeyUsages []string
var upn string

for _, extension := range cert.Extensions {
if extension.Id.Equal(winpki.EnhancedKeyUsageExtensionOID) {
var oids []asn1.ObjectIdentifier
if _, err := asn1.Unmarshal(extension.Value, &oids); err == nil {
enhancedKeyUsages = make([]string, 0, len(oids))
for _, oid := range oids {
enhancedKeyUsages = append(enhancedKeyUsages, oid.String())
}
}
} else if extension.Id.Equal(winpki.SubjectAltNameExtensionOID) {
var san winpki.SubjectAltName[winpki.UPN]
if _, err := asn1.Unmarshal(extension.Value, &san); err == nil {
upn = san.OtherName.Value.Value
}
}
}

metadata.Subject = cert.Subject.String()
metadata.SerialNumber = cert.SerialNumber.String()
metadata.UPN = upn
metadata.CRLDistributionPoints = cert.CRLDistributionPoints
metadata.KeyUsage = int32(cert.KeyUsage)
metadata.ExtendedKeyUsage = slices.Map(cert.ExtKeyUsage, func(eku x509.ExtKeyUsage) int32 { return int32(eku) })
metadata.EnhancedKeyUsage = enhancedKeyUsages
}

func (s *WindowsService) makeTDPSendHandler(
ctx context.Context,
recorder libevents.SessionPreparerRecorder,
Expand Down
2 changes: 1 addition & 1 deletion lib/tlsca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,7 @@ func (c *CertificateRequest) CheckAndSetDefaults() error {
return trace.BadParameter("missing parameter PublicKey")
}
if c.Subject.CommonName == "" {
return trace.BadParameter("missing parameter Subject.Common name")
return trace.BadParameter("missing parameter Subject.CommonName")
}
if c.NotAfter.IsZero() {
return trace.BadParameter("missing parameter NotAfter")
Expand Down
Loading