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
3 changes: 3 additions & 0 deletions integrations/event-handler/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.2.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/subscription/armsubscription v1.2.0 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
Expand Down Expand Up @@ -141,12 +142,14 @@ require (
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-chi/chi/v5 v5.2.2 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-ldap/ldap/v3 v3.4.11 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.23.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions integrations/event-handler/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAu
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0=
github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
Comment thread
zmb3 marked this conversation as resolved.
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
Expand Down
28 changes: 17 additions & 11 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@ import (
"crypto/rand"
"crypto/subtle"
"crypto/x509"
"encoding/base32"
"encoding/pem"
"errors"
"fmt"
"io"
"log/slog"
"maps"
"math/big"
mathrand "math/rand/v2"
"net"
"os"
Expand Down Expand Up @@ -3127,7 +3127,7 @@ func (a *Server) augmentUserCertificates(
}

// Issue audit event on success, same as [Server.generateCert].
a.emitCertCreateEvent(ctx, newIdentity, notAfter)
a.emitCertCreateEvent(ctx, tlsCA, newIdentity, notAfter)

return &proto.Certs{
SSH: newAuthorizedKey,
Expand Down Expand Up @@ -3536,6 +3536,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types.
}

var signedTLSCert []byte
var tlsIssuer *tlsca.CertAuthority
if req.tlsPublicKey != nil {
tlsCryptoPubKey, err := keys.ParsePublicKey(req.tlsPublicKey)
if err != nil {
Expand Down Expand Up @@ -3563,9 +3564,10 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types.
if err != nil {
return nil, trace.Wrap(err)
}
tlsIssuer = tlsCA
}

a.emitCertCreateEvent(ctx, &identity, notAfter)
a.emitCertCreateEvent(ctx, tlsIssuer, &identity, notAfter)

// create certs struct to return to user
certs := &proto.Certs{
Expand Down Expand Up @@ -3750,9 +3752,17 @@ func (a *Server) getSigningCAs(ctx context.Context, domainName string, caType ty
return tlsCA, sshSigner, ca, nil
}

func (a *Server) emitCertCreateEvent(ctx context.Context, identity *tlsca.Identity, notAfter time.Time) {
func (a *Server) emitCertCreateEvent(ctx context.Context, issuer *tlsca.CertAuthority, identity *tlsca.Identity, notAfter time.Time) {
eventIdentity := identity.GetEventIdentity()
eventIdentity.Expires = notAfter
var certAuthority *apievents.CertificateAuthority
if issuer != nil {
certAuthority = &apievents.CertificateAuthority{
Type: string(types.UserCA),
Domain: issuer.Cert.Issuer.CommonName,
SubjectKeyID: base32.HexEncoding.EncodeToString(issuer.Cert.SubjectKeyId),
Comment thread
zmb3 marked this conversation as resolved.
}
}
if err := a.emitter.EmitAuditEvent(a.closeCtx, &apievents.CertificateCreate{
Metadata: apievents.Metadata{
Type: events.CertificateCreateEvent,
Expand All @@ -3765,6 +3775,7 @@ func (a *Server) emitCertCreateEvent(ctx context.Context, identity *tlsca.Identi
// fetched. Need to propagate user-agent from HTTP calls.
UserAgent: trimUserAgent(metadata.UserAgentFromContext(ctx)),
},
CertificateAuthority: certAuthority,
}); err != nil {
a.logger.WarnContext(ctx, "Failed to emit certificate create event", "error", err)
}
Expand Down Expand Up @@ -6683,13 +6694,8 @@ func (a *Server) GenerateCertAuthorityCRL(ctx context.Context, caType types.Cert
if err != nil {
return nil, trace.Wrap(err)
}
// Empty CRL valid for 1yr.
template := &x509.RevocationList{
Number: big.NewInt(1),
ThisUpdate: time.Now().Add(-1 * time.Minute), // 1 min in the past to account for clock skew.
NextUpdate: time.Now().Add(365 * 24 * time.Hour),
}
crl, err := x509.CreateRevocationList(rand.Reader, template, tlsAuthority.Cert, tlsAuthority.Signer)

crl, err := keystore.GenerateCRL(tlsAuthority.Cert, tlsAuthority.Signer)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
5 changes: 5 additions & 0 deletions lib/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2583,9 +2583,14 @@ func TestGenerateUserCertWithCertExtension(t *testing.T) {
ClientMetadata: apievents.ClientMetadata{
UserAgent: "test-user-agent/1.0",
},
CertificateAuthority: &apievents.CertificateAuthority{
Type: "user",
Domain: "test.localhost",
},
},
lastEvent.(*apievents.CertificateCreate),
cmpopts.IgnoreFields(apievents.Identity{}, "Logins", "Expires"),
cmpopts.IgnoreFields(apievents.CertificateAuthority{}, "SubjectKeyID"),
))
}

Expand Down
5 changes: 5 additions & 0 deletions lib/auth/bot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,10 +497,15 @@ func TestRegisterBotInstance(t *testing.T) {
BotName: "test",
BotInstanceID: ident.BotInstanceID,
},
CertificateAuthority: &events.CertificateAuthority{
Type: "user",
Domain: "localhost",
},
},
cmpopts.IgnoreFields(events.Metadata{}, "Time"),
cmpopts.IgnoreFields(events.Identity{}, "Logins", "Expires"),
cmpopts.IgnoreFields(events.ClientMetadata{}, "UserAgent"),
cmpopts.IgnoreFields(events.CertificateAuthority{}, "SubjectKeyID"),
cmpopts.EquateEmpty(),
),
)
Expand Down
19 changes: 17 additions & 2 deletions lib/auth/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/winpki"
)

// GenerateDatabaseCert generates client certificate used by a database
Expand Down Expand Up @@ -155,14 +156,28 @@ func (a *Server) generateDatabaseCert(ctx context.Context, req *proto.DatabaseCe
Subject: csr.Subject,
NotAfter: a.clock.Now().UTC().Add(req.TTL.Get()),
}

if req.CertificateExtensions == proto.DatabaseCertRequest_WINDOWS_SMARTCARD {
// Pass through ExtKeyUsage (which we need for Smartcard Logon usage)
// and SubjectAltName (which we need for otherName SAN, not supported
// out of the box in crypto/x509) extensions only.
certReq.ExtraExtensions = filterExtensions(a.CloseContext(), a.logger, csr.Extensions, oidExtKeyUsage, oidSubjectAltName, oidADUserMapping)
certReq.KeyUsage = x509.KeyUsageDigitalSignature
// CRL is required for Windows smartcard certs.
certReq.CRLDistributionPoints = []string{req.CRLEndpoint}
// CRL Distribution Points (CDP) are required for Windows smartcard certs.
// The CDP is computed here by the auth server issuing the cert and not provided
// by the client because the CDP is based on the identity of the issuer, which is
// necessary in order to support clusters with multiple issuing certs (HSMs).
// If there's only 1 active key we don't include SKID in CDP for backward compatibility.
if req.CRLDomain != "" {
includeSKID := len(ca.GetActiveKeys().TLS) > 1
cdp := winpki.CRLDistributionPoint(req.CRLDomain, types.DatabaseClientCA, tlsCA, includeSKID)
certReq.CRLDistributionPoints = []string{cdp}
} else if req.CRLEndpoint != "" {
// legacy clients will specify CRL endpoint instead of CRL domain
// DELETE IN v20 (zmb3)
Comment thread
probakowski marked this conversation as resolved.
certReq.CRLDistributionPoints = []string{req.CRLEndpoint}
a.logger.DebugContext(ctx, "Generating Database cert with legacy CDP")
}
} else {
// Include provided server names as SANs in the certificate, CommonName
// has been deprecated since Go 1.15:
Expand Down
24 changes: 19 additions & 5 deletions lib/auth/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/winpki"
)

// GenerateWindowsDesktopCert generates client certificate for Windows RDP
Expand All @@ -48,6 +49,12 @@ func (a *Server) GenerateWindowsDesktopCert(ctx context.Context, req *proto.Wind
return nil, trace.AccessDenied(
"this Teleport cluster is not licensed for desktop access, please contact the cluster administrator")
}

limitExceeded, err := a.desktopsLimitExceeded(ctx)
if err != nil {
return nil, trace.Wrap(err)
}

csr, err := tlsca.ParseCertificateRequestPEM(req.CSR)
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -71,6 +78,7 @@ func (a *Server) GenerateWindowsDesktopCert(ctx context.Context, req *proto.Wind
if err != nil {
return nil, trace.Wrap(err)
}

// See https://docs.microsoft.com/en-us/troubleshoot/windows-server/windows-security/enabling-smart-card-logon-third-party-certification-authorities
// for cert requirements for Windows authn.
certReq := tlsca.CertificateRequest{
Expand All @@ -85,14 +93,20 @@ func (a *Server) GenerateWindowsDesktopCert(ctx context.Context, req *proto.Wind
// CRL Distribution Points (CDP) are required for Windows smartcard certs
// for users wanting to RDP. They are not required for the service account
// cert that Teleport itself uses to authenticate for LDAP.
if req.CRLEndpoint != "" {
//
// The CDP is computed here by the auth server issuing the cert and not provided
// by the client because the CDP is based on the identity of the issuer, which is
// necessary in order to support clusters with multiple issuing certs (HSMs).
if req.CRLDomain != "" {
cdp := winpki.CRLDistributionPoint(req.CRLDomain, types.UserCA, tlsCA, true)
certReq.CRLDistributionPoints = []string{cdp}
} else if req.CRLEndpoint != "" {
// legacy clients will specify CRL endpoint instead of CRL domain
// DELETE IN v21 (zmb3)
certReq.CRLDistributionPoints = []string{req.CRLEndpoint}
a.logger.DebugContext(ctx, "Generating Windows desktop cert with legacy CDP")
}

limitExceeded, err := a.desktopsLimitExceeded(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
certReq.ExtraExtensions = append(certReq.ExtraExtensions, pkix.Extension{
Id: tlsca.LicenseOID,
Value: []byte(modules.GetModules().BuildType()),
Expand Down
29 changes: 29 additions & 0 deletions lib/auth/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,35 @@ func initializeAuthority(ctx context.Context, asrv *Server, caID types.CertAuthI
}
}

// Add [empty] CRLs to any issuers that are missing them.
// These are valid for 10 years and regenerated on CA rotation.
updated := false
Comment thread
probakowski marked this conversation as resolved.
for _, kp := range ca.GetActiveKeys().TLS {
if kp.CRL != nil {
continue
}
certBytes, signer, err := asrv.keyStore.GetTLSCertAndSigner(ctx, ca)
if err != nil {
return nil, nil, trace.Wrap(err)
}
cert, err := tlsca.ParseCertificatePEM(certBytes)
if err != nil {
return nil, nil, trace.Wrap(err)
}
crl, err := keystore.GenerateCRL(cert, signer)
if err != nil {
return nil, nil, trace.Wrap(err)
}
kp.CRL = crl
updated = true
}

if updated {
if ca, err = asrv.UpdateCertAuthority(ctx, ca); err != nil {
return nil, nil, trace.Wrap(err)
}
}

// Make sure the keystore has usable keys. This is a bit redundant if the CA
// was just generated above, but cheap relative to generating the CA, and
// it's nice to get the usableKeysResult.
Expand Down
33 changes: 31 additions & 2 deletions lib/auth/keystore/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"io"
"log/slog"
"maps"
"math/big"
"slices"
"time"

kms "cloud.google.com/go/kms/apiv1"
"github.com/gravitational/trace"
Expand Down Expand Up @@ -595,15 +598,41 @@ func (m *Manager) newTLSKeyPair(ctx context.Context, clusterName string, alg cry
if err != nil {
return nil, trace.Wrap(err)
}

certificate, err := tlsca.ParseCertificatePEM(tlsCert)
Comment thread
probakowski marked this conversation as resolved.
if err != nil {
return nil, trace.Wrap(err)
}

crl, err := GenerateCRL(certificate, signer)
if err != nil {
return nil, err
}
return &types.TLSKeyPair{
Cert: tlsCert,
Key: tlsKey,
KeyType: keyType(tlsKey),
CRL: crl,
}, nil
}

// New JWTKeyPair create a new JWT keypair in the keystore backend and returns
// it.
// GenerateCRL generates an empty x509 certificate revocation list.
func GenerateCRL(caCert *x509.Certificate, signer crypto.Signer) ([]byte, error) {
Comment thread
probakowski marked this conversation as resolved.
Comment thread
zmb3 marked this conversation as resolved.
revocationList := &x509.RevocationList{
Number: big.NewInt(1),
ThisUpdate: time.Now().Add(-1 * time.Minute), // 1 min in the past to account for clock skew.

// Note the 10 year expiration date. CRLs are always empty, so they don't need to change frequently.
NextUpdate: time.Now().Add(10 * 365 * 24 * time.Hour),
}
crl, err := x509.CreateRevocationList(rand.Reader, revocationList, caCert, signer)
if err != nil {
return nil, trace.Wrap(err, "generating CRL")
}
return crl, nil
}

// NewJWTKeyPair create a new JWT keypair in the keystore backend and returns it.
func (m *Manager) NewJWTKeyPair(ctx context.Context, purpose cryptosuites.KeyPurpose) (*types.JWTKeyPair, error) {
alg, err := cryptosuites.AlgorithmForKey(ctx, m.currentSuiteGetter, purpose)
if err != nil {
Expand Down
Loading
Loading