Skip to content

Commit

Permalink
[v12] Metrics: add IsSSO to Discover Events (#23902)
Browse files Browse the repository at this point in the history
* Metrics: add IsSSO to Discover Events

* review pt1

* use services.UserGetter interface

* godocs, renamings

* add isSSO to user identity

* remove ctx from convert

* add godocs to usermetadata fields

* add UserType enum
  • Loading branch information
marcoandredinis committed Apr 3, 2023
1 parent 2efe276 commit 6397af5
Show file tree
Hide file tree
Showing 15 changed files with 773 additions and 603 deletions.
21 changes: 21 additions & 0 deletions api/types/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ import (
"github.com/gravitational/teleport/api/utils"
)

// UserType is the user's types that indicates where it was created.
type UserType string

const (
// UserTypeSSO identifies a user that was created from an SSO provider.
UserTypeSSO UserType = "sso"
// UserTypeLocal identifies a user that was created in Teleport itself and has no connection to an external identity.
UserTypeLocal UserType = "local"
)

// User represents teleport embedded user or external user.
type User interface {
// ResourceWithSecrets provides common resource properties
Expand Down Expand Up @@ -98,6 +108,8 @@ type User interface {
GetCreatedBy() CreatedBy
// SetCreatedBy sets created by information
SetCreatedBy(CreatedBy)
// GetUserType indicates if the User was created by an SSO Provider or locally.
GetUserType() UserType
// GetTraits gets the trait map for this user used to populate role variables.
GetTraits() map[string][]string
// SetTraits sets the trait map for this user used to populate role variables.
Expand Down Expand Up @@ -393,6 +405,15 @@ func (u UserV2) GetGCPServiceAccounts() []string {
return u.getTrait(constants.TraitGCPServiceAccounts)
}

// GetUserType indicates if the User was created by an SSO Provider or locally.
func (u UserV2) GetUserType() UserType {
if u.GetCreatedBy().Connector == nil {
return UserTypeLocal
}

return UserTypeSSO
}

func (u *UserV2) String() string {
return fmt.Sprintf("User(name=%v, roles=%v, identities=%v)", u.Metadata.Name, u.Spec.Roles, u.Spec.OIDCIdentities)
}
Expand Down
1,148 changes: 579 additions & 569 deletions gen/proto/go/prehog/v1alpha/teleport.pb.go

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions gen/proto/js/prehog/v1alpha/teleport_pb.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 31 additions & 1 deletion gen/proto/js/prehog/v1alpha/teleport_pb.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1978,6 +1978,7 @@ func (a *Server) generateUserCert(req certRequest) (*proto.Certs, error) {
AssetTag: req.deviceExtensions.AssetTag,
CredentialID: req.deviceExtensions.CredentialID,
},
UserType: req.user.GetUserType(),
}
subject, err := identity.Subject()
if err != nil {
Expand Down Expand Up @@ -4190,7 +4191,17 @@ func (a *Server) SubmitUsageEvent(ctx context.Context, req *proto.SubmitUsageEve
return trace.Wrap(err)
}

event, err := usagereporter.ConvertUsageEvent(req.GetEvent(), username)
userIsSSO, err := authz.GetClientUserIsSSO(ctx)
if err != nil {
return trace.Wrap(err)
}

userMetadata := usagereporter.UserMetadata{
Username: username,
IsSSO: userIsSSO,
}

event, err := usagereporter.ConvertUsageEvent(req.GetEvent(), userMetadata)
if err != nil {
return trace.Wrap(err)
}
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -2588,7 +2588,7 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC
}

// Do not allow SSO users to be impersonated.
if req.Username != a.context.User.GetName() && user.GetCreatedBy().Connector != nil {
if req.Username != a.context.User.GetName() && user.GetUserType() == types.UserTypeSSO {
log.Warningf("User %v tried to issue a cert for externally managed user %v, this is not supported.", a.context.User.GetName(), req.Username)
return nil, trace.AccessDenied("access denied")
}
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ func (s *Server) generateInitialBotCerts(ctx context.Context, username string, p
}

// Do not allow SSO users to be impersonated.
if user.GetCreatedBy().Connector != nil {
if user.GetUserType() == types.UserTypeSSO {
log.Warningf("Tried to issue a renewable cert for externally managed user %v, this is not supported.", username)
return nil, trace.AccessDenied("access denied")
}
Expand Down
34 changes: 28 additions & 6 deletions lib/authz/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ func (a *authorizer) authorizeRemoteUser(ctx context.Context, u RemoteUser) (*Co
MFAVerified: u.Identity.MFAVerified,
LoginIP: u.Identity.LoginIP,
PrivateKeyPolicy: u.Identity.PrivateKeyPolicy,
UserType: u.Identity.UserType,
}

return &Context{
Expand Down Expand Up @@ -936,21 +937,42 @@ func ClientUsername(ctx context.Context) string {
return identity.Username
}

// GetClientUsername 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, returns an error.
func GetClientUsername(ctx context.Context) (string, error) {
func userIdentityFromContext(ctx context.Context) (*tlsca.Identity, error) {
userWithIdentity, err := UserFromContext(ctx)
if err != nil {
return "", trace.AccessDenied("missing identity")
return nil, trace.AccessDenied("missing identity")
}

identity := userWithIdentity.GetIdentity()
if identity.Username == "" {
return "", trace.AccessDenied("missing identity username")
return nil, trace.AccessDenied("missing identity username")
}

return &identity, nil
}

// GetClientUsername 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, returns an error.
func GetClientUsername(ctx context.Context) (string, error) {
identity, err := userIdentityFromContext(ctx)
if err != nil {
return "", trace.Wrap(err)
}
return identity.Username, nil
}

// GetClientUserIsSSO extracts the identity of a remote HTTP client and indicates whether that is an SSO user.
// If ctx didn't pass through auth middleware or did not come from an HTTP
// request, returns an error.
func GetClientUserIsSSO(ctx context.Context) (bool, error) {
identity, err := userIdentityFromContext(ctx)
if err != nil {
return false, trace.Wrap(err)
}
return identity.UserType == types.UserTypeSSO, nil
}

// ClientImpersonator returns the impersonator username of a remote client
// making the call. If not present, returns an empty string
func ClientImpersonator(ctx context.Context) string {
Expand Down
26 changes: 26 additions & 0 deletions lib/authz/permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,32 @@ func upsertLockWithPutEvent(ctx context.Context, t *testing.T, client *testClien
}
}

func TestGetClientUserIsSSO(t *testing.T) {
ctx := context.Background()

u := LocalUser{
Username: "someuser",
Identity: tlsca.Identity{
Username: "someuser",
Groups: []string{"somerole"},
},
}

// Non SSO user must return false
nonSSOUserCtx := context.WithValue(ctx, contextUser, u)

isSSO, err := GetClientUserIsSSO(nonSSOUserCtx)
require.NoError(t, err)
require.False(t, isSSO, "expected a non-SSO user")

// An SSO user must return true
u.Identity.UserType = types.UserTypeSSO
ssoUserCtx := context.WithValue(ctx, contextUser, u)
localUserIsSSO, err := GetClientUserIsSSO(ssoUserCtx)
require.NoError(t, err)
require.True(t, localUserIsSSO, "expected an SSO user")
}

func TestAuthorizer_Authorize_deviceTrust(t *testing.T) {
t.Parallel()

Expand Down
3 changes: 3 additions & 0 deletions lib/tlsca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ type Identity struct {

// DeviceExtensions holds device-aware extensions for the identity.
DeviceExtensions DeviceExtensions

// UserType indicates if the User was created by an SSO Provider or locally.
UserType types.UserType
}

// RouteToApp holds routing information for applications.
Expand Down

0 comments on commit 6397af5

Please sign in to comment.