Adjust clientIP/pinnedIP fields according to IP pinning RFD (#21866)

* Adjust clientIP/pinnedIP fields according to IP pinning RFD

* Improve wording.

Co-authored-by: Noah Stride <noah.stride@goteleport.com>

---------

Co-authored-by: Noah Stride <noah.stride@goteleport.com>
This commit is contained in:
Anton Miniailo 2023-02-15 19:36:19 +00:00 committed by GitHub
parent 03184ee32c
commit 13fad744d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 126 additions and 94 deletions

View file

@ -431,9 +431,9 @@ const (
// deadline in cases where both require_session_mfa and disconnect_expired_cert
// are enabled. See https://github.com/gravitational/teleport/issues/18544.
CertExtensionPreviousIdentityExpires = "prev-identity-expires"
// CertExtensionClientIP is used to embed the IP of the client that created
// CertExtensionLoginIP is used to embed the IP of the client that created
// the certificate.
CertExtensionClientIP = "client-ip"
CertExtensionLoginIP = "login-ip"
// CertExtensionImpersonator is set when one user has requested certificates
// for another user
CertExtensionImpersonator = "impersonator"

View file

@ -1120,10 +1120,10 @@ type certRequest struct {
// deadline in cases where both require_session_mfa and disconnect_expired_cert
// are enabled. See https://github.com/gravitational/teleport/issues/18544.
previousIdentityExpires time.Time
// clientIP is an IP of the client requesting the certificate.
clientIP string
// sourceIP is an IP this certificate should be pinned to
sourceIP string
// loginIP is an IP of the client requesting the certificate.
loginIP string
// pinIP flags that client's login IP should be pinned in the certificate
pinIP bool
// disallowReissue flags that a cert should not be allowed to issue future
// certificates.
disallowReissue bool
@ -1177,8 +1177,8 @@ func certRequestPreviousIdentityExpires(previousIdentityExpires time.Time) certR
return func(r *certRequest) { r.previousIdentityExpires = previousIdentityExpires }
}
func certRequestClientIP(ip string) certRequestOption {
return func(r *certRequest) { r.clientIP = ip }
func certRequestLoginIP(ip string) certRequestOption {
return func(r *certRequest) { r.loginIP = ip }
}
func certRequestDeviceExtensions(ext tlsca.DeviceExtensions) certRequestOption {
@ -1188,7 +1188,7 @@ func certRequestDeviceExtensions(ext tlsca.DeviceExtensions) certRequestOption {
}
// GenerateUserTestCerts is used to generate user certificate, used internally for tests
func (a *Server) GenerateUserTestCerts(key []byte, username string, ttl time.Duration, compatibility, routeToCluster, sourceIP string) ([]byte, []byte, error) {
func (a *Server) GenerateUserTestCerts(key []byte, username string, ttl time.Duration, compatibility, routeToCluster, pinnedIP string) ([]byte, []byte, error) {
user, err := a.GetUser(username, false)
if err != nil {
return nil, nil, trace.Wrap(err)
@ -1210,7 +1210,8 @@ func (a *Server) GenerateUserTestCerts(key []byte, username string, ttl time.Dur
routeToCluster: routeToCluster,
checker: checker,
traits: user.GetTraits(),
sourceIP: sourceIP,
loginIP: pinnedIP,
pinIP: pinnedIP != "",
})
if err != nil {
return nil, nil, trace.Wrap(err)
@ -1749,16 +1750,14 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) {
}
pinnedIP := ""
if req.checker.PinSourceIP() {
if req.clientIP == "" && req.sourceIP == "" {
// TODO(anton): make sure all upstream callers provide clientIP and make this into hard error instead of warning.
if req.checker.PinSourceIP() || req.pinIP {
if req.loginIP == "" {
// TODO(anton): make sure all upstream callers provide clientIP and make this into hard error
// instead of warning, after merging #21080
log.Warnf("IP pinning is enabled for user %q but there is no client ip information", req.user.GetName())
}
pinnedIP = req.clientIP
if req.sourceIP != "" {
pinnedIP = req.sourceIP
}
pinnedIP = req.loginIP
}
params := services.UserCertParams{
@ -1778,13 +1777,13 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) {
ActiveRequests: req.activeRequests,
MFAVerified: req.mfaVerified,
PreviousIdentityExpires: req.previousIdentityExpires,
ClientIP: req.clientIP,
LoginIP: req.loginIP,
PinnedIP: pinnedIP,
DisallowReissue: req.disallowReissue,
Renewable: req.renewable,
Generation: req.generation,
CertificateExtensions: req.checker.CertificateExtensions(),
AllowedResourceIDs: requestedResourcesStr,
SourceIP: pinnedIP,
ConnectionDiagnosticID: req.connectionDiagnosticID,
PrivateKeyPolicy: attestedKeyPolicy,
DeviceID: req.deviceExtensions.DeviceID,
@ -1871,7 +1870,8 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) {
DatabaseUsers: dbUsers,
MFAVerified: req.mfaVerified,
PreviousIdentityExpires: req.previousIdentityExpires,
ClientIP: req.clientIP,
LoginIP: req.loginIP,
PinnedIP: pinnedIP,
AWSRoleARNs: roleARNs,
AzureIdentities: azureIdentities,
GCPServiceAccounts: gcpAccounts,

View file

@ -1750,9 +1750,18 @@ func TestGenerateUserCertIPPinning(t *testing.T) {
err = s.a.UpsertRole(ctx, pinnedRole)
require.NoError(t, err)
findTLSClientIP := func(names []pkix.AttributeTypeAndValue) any {
findTLSLoginIP := func(names []pkix.AttributeTypeAndValue) any {
for _, name := range names {
if name.Type.Equal(tlsca.ClientIPASN1ExtensionOID) {
if name.Type.Equal(tlsca.LoginIPASN1ExtensionOID) {
return name.Value
}
}
return nil
}
findTLSPinnedIP := func(names []pkix.AttributeTypeAndValue) any {
for _, name := range names {
if name.Type.Equal(tlsca.PinnedIPASN1ExtensionOID) {
return name.Value
}
}
@ -1762,13 +1771,13 @@ func TestGenerateUserCertIPPinning(t *testing.T) {
testCases := []struct {
desc string
user string
clientIP string
loginIP string
wantPinned bool
}{
{desc: "no client ip, not pinned", user: unpinnedUser, clientIP: "", wantPinned: false},
{desc: "client ip, not pinned", user: unpinnedUser, clientIP: "1.2.3.4", wantPinned: false},
{desc: "client ip, pinned", user: pinnedUser, clientIP: "1.2.3.4", wantPinned: true},
{desc: "no client ip, pinned", user: pinnedUser, clientIP: "", wantPinned: true},
{desc: "no client ip, not pinned", user: unpinnedUser, loginIP: "", wantPinned: false},
{desc: "client ip, not pinned", user: unpinnedUser, loginIP: "1.2.3.4", wantPinned: false},
{desc: "client ip, pinned", user: pinnedUser, loginIP: "1.2.3.4", wantPinned: true},
{desc: "no client ip, pinned", user: pinnedUser, loginIP: "", wantPinned: true},
}
baseAuthRequest := AuthenticateSSHRequest{
@ -1784,13 +1793,13 @@ func TestGenerateUserCertIPPinning(t *testing.T) {
t.Run(tt.desc, func(t *testing.T) {
authRequest := baseAuthRequest
authRequest.AuthenticateUserRequest.Username = tt.user
if tt.clientIP != "" {
if tt.loginIP != "" {
authRequest.ClientMetadata = &ForwardedClientMetadata{
RemoteAddr: tt.clientIP,
RemoteAddr: tt.loginIP,
}
}
resp, err := s.a.AuthenticateSSHUser(ctx, authRequest)
if tt.wantPinned && tt.clientIP == "" {
if tt.wantPinned && tt.loginIP == "" {
require.ErrorContains(t, err, "source IP pinning is enabled but client IP is unknown")
return
}
@ -1803,26 +1812,32 @@ func TestGenerateUserCertIPPinning(t *testing.T) {
tlsCert, err := tlsca.ParseCertificatePEM(resp.TLSCert)
require.NoError(t, err)
tlsClientIP := findTLSClientIP(tlsCert.Subject.Names)
sshClientIP, sshClientIPOK := sshCert.Extensions[teleport.CertExtensionClientIP]
tlsLoginIP := findTLSLoginIP(tlsCert.Subject.Names)
tlsPinnedIP := findTLSPinnedIP(tlsCert.Subject.Names)
sshLoginIP, sshLoginIPOK := sshCert.Extensions[teleport.CertExtensionLoginIP]
sshCriticalAddress, sshCriticalAddressOK := sshCert.CriticalOptions["source-address"]
if tt.clientIP != "" {
require.NotNil(t, tlsClientIP, "client IP not found on TLS cert")
require.Equal(t, tlsClientIP, tt.clientIP, "TLS ClientIP mismatch")
if tt.loginIP != "" {
require.NotNil(t, tlsLoginIP, "client IP not found on TLS cert")
require.Equal(t, tlsLoginIP, tt.loginIP, "TLS LoginIP mismatch")
require.True(t, sshClientIPOK, "SSH ClientIP extension not present")
require.Equal(t, tt.clientIP, sshClientIP, "SSH ClientIP mismatch")
require.True(t, sshLoginIPOK, "SSH LoginIP extension not present")
require.Equal(t, tt.loginIP, sshLoginIP, "SSH LoginIP mismatch")
} else {
require.Nil(t, tlsClientIP, "client IP unexpectedly found on TLS cert")
require.Nil(t, tlsLoginIP, "client IP unexpectedly found on TLS cert")
require.False(t, sshClientIPOK, "client IP unexpectedly found on SSH cert")
require.False(t, sshLoginIPOK, "client IP unexpectedly found on SSH cert")
}
if tt.wantPinned {
require.NotNil(t, tlsPinnedIP, "pinned IP not found on TLS cert")
require.Equal(t, tt.loginIP, tlsPinnedIP, "pinned IP on TLS cert mismatch")
require.True(t, sshCriticalAddressOK, "source address not found on SSH cert")
require.Equal(t, tt.clientIP+"/32", sshCriticalAddress, "SSH source address mismatch")
require.Equal(t, tt.loginIP+"/32", sshCriticalAddress, "SSH source address mismatch")
} else {
require.Nil(t, tlsPinnedIP, "pinned IP unexpectedly found on TLS cert")
require.False(t, sshCriticalAddressOK, "source address unexpectedly found on SSH cert")
}
})

View file

@ -2716,8 +2716,8 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
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.
clientIP: a.context.Identity.GetIdentity().ClientIP,
traits: accessInfo.Traits,
loginIP: a.context.Identity.GetIdentity().LoginIP,
traits: accessInfo.Traits,
activeRequests: services.RequestIDs{
AccessRequests: req.AccessRequests,
},

View file

@ -2582,7 +2582,7 @@ func userSingleUseCertsGenerate(ctx context.Context, actx *grpcContext, req prot
ctx, req,
certRequestMFAVerified(mfaDev.Id),
certRequestPreviousIdentityExpires(actx.Identity.GetIdentity().Expires),
certRequestClientIP(clientIP),
certRequestLoginIP(clientIP),
certRequestDeviceExtensions(actx.Identity.GetIdentity().DeviceExtensions),
)
if err != nil {

View file

@ -1265,7 +1265,7 @@ func TestGenerateUserSingleUseCert(t *testing.T) {
require.Equal(t, webDevID, cert.Extensions[teleport.CertExtensionMFAVerified])
require.Equal(t, userCertExpires.Format(time.RFC3339), cert.Extensions[teleport.CertExtensionPreviousIdentityExpires])
require.True(t, net.ParseIP(cert.Extensions[teleport.CertExtensionClientIP]).IsLoopback())
require.True(t, net.ParseIP(cert.Extensions[teleport.CertExtensionLoginIP]).IsLoopback())
require.Equal(t, uint64(clock.Now().Add(teleport.UserSingleUseCertTTL).Unix()), cert.ValidBefore)
},
},
@ -1294,7 +1294,7 @@ func TestGenerateUserSingleUseCert(t *testing.T) {
require.Equal(t, webDevID, cert.Extensions[teleport.CertExtensionMFAVerified])
require.Equal(t, userCertExpires.Format(time.RFC3339), cert.Extensions[teleport.CertExtensionPreviousIdentityExpires])
require.True(t, net.ParseIP(cert.Extensions[teleport.CertExtensionClientIP]).IsLoopback())
require.True(t, net.ParseIP(cert.Extensions[teleport.CertExtensionLoginIP]).IsLoopback())
require.Equal(t, uint64(clock.Now().Add(teleport.UserSingleUseCertTTL).Unix()), cert.ValidBefore)
},
},
@ -1326,7 +1326,7 @@ func TestGenerateUserSingleUseCert(t *testing.T) {
require.NoError(t, err)
require.Equal(t, webDevID, identity.MFAVerified)
require.Equal(t, userCertExpires, identity.PreviousIdentityExpires)
require.True(t, net.ParseIP(identity.ClientIP).IsLoopback())
require.True(t, net.ParseIP(identity.LoginIP).IsLoopback())
require.Equal(t, []string{teleport.UsageKubeOnly}, identity.Usage)
require.Equal(t, "kube-a", identity.KubernetesCluster)
},
@ -1361,7 +1361,7 @@ func TestGenerateUserSingleUseCert(t *testing.T) {
require.NoError(t, err)
require.Equal(t, webDevID, identity.MFAVerified)
require.Equal(t, userCertExpires, identity.PreviousIdentityExpires)
require.True(t, net.ParseIP(identity.ClientIP).IsLoopback())
require.True(t, net.ParseIP(identity.LoginIP).IsLoopback())
require.Equal(t, []string{teleport.UsageDatabaseOnly}, identity.Usage)
require.Equal(t, identity.RouteToDatabase.ServiceName, "db-a")
},
@ -1398,7 +1398,7 @@ func TestGenerateUserSingleUseCert(t *testing.T) {
require.NoError(t, err)
require.Equal(t, webDevID, identity.MFAVerified)
require.Equal(t, userCertExpires, identity.PreviousIdentityExpires)
require.True(t, net.ParseIP(identity.ClientIP).IsLoopback())
require.True(t, net.ParseIP(identity.LoginIP).IsLoopback())
require.Equal(t, []string{teleport.UsageDatabaseOnly}, identity.Usage)
require.Equal(t, identity.RouteToDatabase.ServiceName, "db-a")
},

View file

@ -195,8 +195,8 @@ func (k *Keygen) GenerateUserCertWithoutValidation(c services.UserCertParams) ([
if !c.PreviousIdentityExpires.IsZero() {
cert.Permissions.Extensions[teleport.CertExtensionPreviousIdentityExpires] = c.PreviousIdentityExpires.Format(time.RFC3339)
}
if c.ClientIP != "" {
cert.Permissions.Extensions[teleport.CertExtensionClientIP] = c.ClientIP
if c.LoginIP != "" {
cert.Permissions.Extensions[teleport.CertExtensionLoginIP] = c.LoginIP
}
if c.Impersonator != "" {
cert.Permissions.Extensions[teleport.CertExtensionImpersonator] = c.Impersonator
@ -229,7 +229,7 @@ func (k *Keygen) GenerateUserCertWithoutValidation(c services.UserCertParams) ([
cert.Permissions.Extensions[teleport.CertExtensionDeviceCredentialID] = credID
}
if c.SourceIP != "" {
if c.PinnedIP != "" {
if modules.GetModules().BuildType() != modules.BuildEnterprise {
return nil, trace.AccessDenied("source IP pinning is only supported in Teleport Enterprise")
}
@ -237,10 +237,10 @@ func (k *Keygen) GenerateUserCertWithoutValidation(c services.UserCertParams) ([
cert.CriticalOptions = make(map[string]string)
}
//IPv4, all bits matter
ip := c.SourceIP + "/32"
if strings.Contains(c.SourceIP, ":") {
ip := c.PinnedIP + "/32"
if strings.Contains(c.PinnedIP, ":") {
//IPv6
ip = c.SourceIP + "/128"
ip = c.PinnedIP + "/128"
}
cert.CriticalOptions[sourceAddress] = ip
}

View file

@ -516,7 +516,7 @@ func (s *Server) AuthenticateSSHUser(ctx context.Context, req AuthenticateSSHReq
traits: user.GetTraits(),
routeToCluster: req.RouteToCluster,
kubernetesCluster: req.KubernetesCluster,
clientIP: clientIP,
loginIP: clientIP,
attestationStatement: req.AttestationStatement,
})
if err != nil {

View file

@ -347,7 +347,7 @@ func (a *authorizer) authorizeRemoteUser(ctx context.Context, u RemoteUser) (*Co
RouteToApp: u.Identity.RouteToApp,
RouteToDatabase: u.Identity.RouteToDatabase,
MFAVerified: u.Identity.MFAVerified,
ClientIP: u.Identity.ClientIP,
LoginIP: u.Identity.LoginIP,
PrivateKeyPolicy: u.Identity.PrivateKeyPolicy,
}

View file

@ -344,10 +344,10 @@ type UserCertParams struct {
// deadline in cases where both require_session_mfa and disconnect_expired_cert
// are enabled. See https://github.com/gravitational/teleport/issues/18544.
PreviousIdentityExpires time.Time
// ClientIP is an IP of the client to embed in the certificate.
ClientIP string
// SourceIP is an IP that certificate should be pinned to.
SourceIP string
// LoginIP is an observed IP of the client on the moment of certificate creation.
LoginIP string
// PinnedIP is an IP from which client must communicate with Teleport.
PinnedIP string
// DisallowReissue flags that any attempt to request new certificates while
// authenticated with this cert should be denied.
DisallowReissue bool

View file

@ -99,7 +99,7 @@ func (a *audit) OnSessionStart(ctx context.Context, serverID string, identity *t
},
UserMetadata: identity.GetUserMetadata(),
ConnectionMetadata: apievents.ConnectionMetadata{
RemoteAddr: identity.ClientIP,
RemoteAddr: identity.LoginIP,
},
AppMetadata: apievents.AppMetadata{
AppURI: app.GetURI(),
@ -128,7 +128,7 @@ func (a *audit) OnSessionEnd(ctx context.Context, serverID string, identity *tls
},
UserMetadata: identity.GetUserMetadata(),
ConnectionMetadata: apievents.ConnectionMetadata{
RemoteAddr: identity.ClientIP,
RemoteAddr: identity.LoginIP,
},
AppMetadata: apievents.AppMetadata{
AppURI: app.GetURI(),

View file

@ -608,7 +608,7 @@ func (s *ProxyServer) Authorize(ctx context.Context, tlsConn utils.TLSConn, para
identity.RouteToDatabase.Database = params.Database
}
if params.ClientIP != "" {
identity.ClientIP = params.ClientIP
identity.LoginIP = params.ClientIP
}
cluster, servers, err := s.getDatabaseServers(ctx, identity)
if err != nil {

View file

@ -912,8 +912,8 @@ func (s *Server) handleConnection(ctx context.Context, clientConn net.Conn) erro
return trace.Wrap(err)
}
// TODO(jakule): ClientIP should be required starting from 10.0.
clientIP := sessionCtx.Identity.ClientIP
// TODO(jakule): LoginIP should be required starting from 10.0.
clientIP := sessionCtx.Identity.LoginIP
if clientIP != "" {
s.log.Debugf("Real client IP %s", clientIP)
@ -924,7 +924,7 @@ func (s *Server) handleConnection(ctx context.Context, clientConn net.Conn) erro
}
defer release()
} else {
s.log.Debug("ClientIP is not set (Proxy Service has to be updated). Rate limiting is disabled.")
s.log.Debug("LoginIP is not set (Proxy Service has to be updated). Rate limiting is disabled.")
}
err = engine.HandleConnection(ctx, sessionCtx)

View file

@ -46,7 +46,7 @@ func (s *WindowsService) onSessionStart(ctx context.Context, emitter events.Emit
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktop.GetAddr(),
Protocol: libevents.EventProtocolTDP,
},
@ -116,7 +116,7 @@ func (s *WindowsService) onClipboardSend(ctx context.Context, emitter events.Emi
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -140,7 +140,7 @@ func (s *WindowsService) onClipboardReceive(ctx context.Context, emitter events.
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -182,7 +182,7 @@ func (s *WindowsService) onSharedDirectoryAnnounce(
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -235,7 +235,7 @@ func (s *WindowsService) onSharedDirectoryAcknowledge(
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -294,7 +294,7 @@ func (s *WindowsService) onSharedDirectoryReadRequest(
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -366,7 +366,7 @@ func (s *WindowsService) onSharedDirectoryReadResponse(
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -428,7 +428,7 @@ func (s *WindowsService) onSharedDirectoryWriteRequest(
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -500,7 +500,7 @@ func (s *WindowsService) onSharedDirectoryWriteResponse(
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},

View file

@ -60,7 +60,7 @@ func setup() (*WindowsService, *tlsca.Identity, *eventstest.MockEmitter) {
Username: "foo",
Impersonator: "bar",
MFAVerified: "mfa-id",
ClientIP: "127.0.0.1",
LoginIP: "127.0.0.1",
}
return s, id, emitter
@ -97,7 +97,7 @@ func TestSessionStartEvent(t *testing.T) {
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktop.GetAddr(),
Protocol: libevents.EventProtocolTDP,
},
@ -310,7 +310,7 @@ func TestDesktopSharedDirectoryStartEvent(t *testing.T) {
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -488,7 +488,7 @@ func TestDesktopSharedDirectoryReadEvent(t *testing.T) {
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -661,7 +661,7 @@ func TestDesktopSharedDirectoryWriteEvent(t *testing.T) {
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -737,7 +737,7 @@ func TestDesktopSharedDirectoryStartEventAuditCacheMax(t *testing.T) {
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -825,7 +825,7 @@ func TestDesktopSharedDirectoryReadEventAuditCacheMax(t *testing.T) {
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},
@ -916,7 +916,7 @@ func TestDesktopSharedDirectoryWriteEventAuditCacheMax(t *testing.T) {
WithMFA: id.MFAVerified,
},
ConnectionMetadata: events.ConnectionMetadata{
LocalAddr: id.ClientIP,
LocalAddr: id.LoginIP,
RemoteAddr: desktopAddr,
Protocol: libevents.EventProtocolTDP,
},

View file

@ -158,8 +158,10 @@ type Identity struct {
// deadline in cases where both require_session_mfa and disconnect_expired_cert
// are enabled. See https://github.com/gravitational/teleport/issues/18544.
PreviousIdentityExpires time.Time
// ClientIP is an observed IP of the client that this Identity represents.
ClientIP string
// LoginIP is an observed IP of the client that this Identity represents.
LoginIP string
// PinnedIP is an IP the certificate is pinned to.
PinnedIP string
// AWSRoleARNs is a list of allowed AWS role ARNs user can assume.
AWSRoleARNs []string
// AzureIdentities is a list of allowed Azure identities user can assume.
@ -310,7 +312,7 @@ func (id *Identity) GetEventIdentity() events.Identity {
DatabaseUsers: id.DatabaseUsers,
MFADeviceUUID: id.MFAVerified,
PreviousIdentityExpires: id.PreviousIdentityExpires,
ClientIP: id.ClientIP,
ClientIP: id.LoginIP,
AWSRoleARNs: id.AWSRoleARNs,
AzureIdentities: id.AzureIdentities,
GCPServiceAccounts: id.GCPServiceAccounts,
@ -370,9 +372,9 @@ var (
// the MFAVerified flag into certificates.
MFAVerifiedASN1ExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 1, 8}
// ClientIPASN1ExtensionOID is an extension ID used when encoding/decoding
// the client IP into certificates.
ClientIPASN1ExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 1, 9}
// LoginIPASN1ExtensionOID is an extension ID used when encoding/decoding
// the client's login IP into certificates.
LoginIPASN1ExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 1, 9}
// AppNameASN1ExtensionOID is an extension ID used when encoding/decoding
// application name into a certificate.
@ -474,6 +476,10 @@ var (
// LicenseOID is an extension OID signaling the license type of Teleport build.
// It should take values "oss" or "ent" (the values returned by modules.GetModules().BuildType())
LicenseOID = asn1.ObjectIdentifier{1, 3, 9999, 2, 14}
// PinnedIPASN1ExtensionOID is an extension ID used when encoding/decoding
// the IP the certificate is pinned to.
PinnedIPASN1ExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 2, 15}
)
// Device Trust OIDs.
@ -645,11 +651,18 @@ func (id *Identity) Subject() (pkix.Name, error) {
Value: id.PreviousIdentityExpires.Format(time.RFC3339),
})
}
if id.ClientIP != "" {
if id.LoginIP != "" {
subject.ExtraNames = append(subject.ExtraNames,
pkix.AttributeTypeAndValue{
Type: ClientIPASN1ExtensionOID,
Value: id.ClientIP,
Type: LoginIPASN1ExtensionOID,
Value: id.LoginIP,
})
}
if id.PinnedIP != "" {
subject.ExtraNames = append(subject.ExtraNames,
pkix.AttributeTypeAndValue{
Type: PinnedIPASN1ExtensionOID,
Value: id.PinnedIP,
})
}
@ -903,10 +916,10 @@ func FromSubject(subject pkix.Name, expires time.Time) (*Identity, error) {
}
id.PreviousIdentityExpires = asTime
}
case attr.Type.Equal(ClientIPASN1ExtensionOID):
case attr.Type.Equal(LoginIPASN1ExtensionOID):
val, ok := attr.Value.(string)
if ok {
id.ClientIP = val
id.LoginIP = val
}
case attr.Type.Equal(DatabaseServiceNameASN1ExtensionOID):
val, ok := attr.Value.(string)
@ -995,6 +1008,10 @@ func FromSubject(subject pkix.Name, expires time.Time) (*Identity, error) {
if val, ok := attr.Value.(string); ok {
id.DeviceExtensions.CredentialID = val
}
case attr.Type.Equal(PinnedIPASN1ExtensionOID):
if val, ok := attr.Value.(string); ok {
id.PinnedIP = val
}
}
}