Skip to content

Commit

Permalink
Access request locks (#9478)
Browse files Browse the repository at this point in the history
* Add access request locks

This only contains the internal part, no user-visible changes

* Add a `tctl lock` flag to specify an access request ID

* Tests for access request locks
  • Loading branch information
espadolini committed Jan 25, 2022
1 parent 08984dd commit 5addc89
Show file tree
Hide file tree
Showing 9 changed files with 694 additions and 598 deletions.
3 changes: 3 additions & 0 deletions api/types/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ func (t LockTarget) Match(lock Lock) bool {
if t.MFADevice != "" && lockTarget.MFADevice != t.MFADevice {
return false
}
if t.AccessRequest != "" && lockTarget.AccessRequest != t.AccessRequest {
return false
}
return true
}

Expand Down
1,204 changes: 625 additions & 579 deletions api/types/types.pb.go

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions api/types/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2300,6 +2300,9 @@ message LockTarget {

// MFADevice specifies the UUID of a user MFA device.
string MFADevice = 5 [ (gogoproto.jsontag) = "mfa_device,omitempty" ];

// AccessRequest specifies the UUID of an access request.
string AccessRequest = 7 [ (gogoproto.jsontag) = "access_request,omitempty" ];
}

// AddressCondition represents a set of addresses. Presently the addresses are specfied
Expand Down
19 changes: 13 additions & 6 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,12 +792,19 @@ func (a *Server) generateUserCert(req certRequest) (*certs, error) {
if err != nil {
return nil, trace.Wrap(err)
}
if lockErr := a.checkLockInForce(req.checker.LockingMode(authPref.GetLockingMode()), append(
services.RolesToLockTargets(req.checker.RoleNames()),
types.LockTarget{User: req.user.GetName()},
types.LockTarget{MFADevice: req.mfaVerified},
)); lockErr != nil {
return nil, trace.Wrap(lockErr)
lockingMode := req.checker.LockingMode(authPref.GetLockingMode())
lockTargets := []types.LockTarget{
{User: req.user.GetName()},
{MFADevice: req.mfaVerified},
}
lockTargets = append(lockTargets,
services.RolesToLockTargets(req.checker.RoleNames())...,
)
lockTargets = append(lockTargets,
services.AccessRequestsToLockTargets(req.activeRequests.AccessRequests)...,
)
if err := a.checkLockInForce(lockingMode, lockTargets); err != nil {
return nil, trace.Wrap(err)
}

// reuse the same RSA keys for SSH and TLS keys
Expand Down
12 changes: 7 additions & 5 deletions lib/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1154,20 +1154,22 @@ func TestGenerateUserCertWithLocks(t *testing.T) {
user, role, err := CreateUserAndRole(p.a, "test-user", []string{})
require.NoError(t, err)
mfaID := uuid.New()
requestID := "test-access-request"
keygen := testauthority.New()
_, pub, err := keygen.GetNewKeyPairFromPool()
require.NoError(t, err)
certReq := certRequest{
user: user,
checker: services.NewRoleSet(role),
mfaVerified: mfaID,
publicKey: pub,
user: user,
checker: services.NewRoleSet(role),
mfaVerified: mfaID,
publicKey: pub,
activeRequests: services.RequestIDs{AccessRequests: []string{requestID}},
}
_, err = p.a.generateUserCert(certReq)
require.NoError(t, err)

testTargets := append(
[]types.LockTarget{{User: user.GetName()}, {MFADevice: mfaID}},
[]types.LockTarget{{User: user.GetName()}, {MFADevice: mfaID}, {AccessRequest: requestID}},
services.RolesToLockTargets(user.GetRoles())...,
)
for _, target := range testTargets {
Expand Down
26 changes: 21 additions & 5 deletions lib/auth/permissions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ func TestAuthorizeWithLocksForLocalUser(t *testing.T) {
localUser := LocalUser{
Username: user.GetName(),
Identity: tlsca.Identity{
Username: user.GetName(),
Groups: []string{"test-role-1"},
MFAVerified: "mfa-device-id",
Username: user.GetName(),
Groups: []string{"test-role-1"},
MFAVerified: "mfa-device-id",
ActiveRequests: []string{"test-request"},
},
}

Expand All @@ -82,7 +83,6 @@ func TestAuthorizeWithLocksForLocalUser(t *testing.T) {
Target: types.LockTarget{MFADevice: localUser.Identity.MFAVerified},
})
require.NoError(t, err)
require.NoError(t, srv.AuthServer.UpsertLock(ctx, mfaLock))
upsertLockWithPutEvent(ctx, t, srv, mfaLock)

_, err = srv.Authorizer.Authorize(context.WithValue(ctx, ContextUser, localUser))
Expand All @@ -94,12 +94,28 @@ func TestAuthorizeWithLocksForLocalUser(t *testing.T) {
_, err = srv.Authorizer.Authorize(context.WithValue(ctx, ContextUser, localUser))
require.NoError(t, err)

// Add an access request lock.
requestLock, err := types.NewLock("request-lock", types.LockSpecV2{
Target: types.LockTarget{AccessRequest: localUser.Identity.ActiveRequests[0]},
})
require.NoError(t, err)
upsertLockWithPutEvent(ctx, t, srv, requestLock)

// localUser's identity with a locked access request is locked out.
_, err = srv.Authorizer.Authorize(context.WithValue(ctx, ContextUser, localUser))
require.Error(t, err)
require.True(t, trace.IsAccessDenied(err))

// Not locked out without the request.
localUser.Identity.ActiveRequests = nil
_, err = srv.Authorizer.Authorize(context.WithValue(ctx, ContextUser, localUser))
require.NoError(t, err)

// Create a lock targeting the role written in the user's identity.
roleLock, err := types.NewLock("role-lock", types.LockSpecV2{
Target: types.LockTarget{Role: localUser.Identity.Groups[0]},
})
require.NoError(t, err)
require.NoError(t, srv.AuthServer.UpsertLock(ctx, roleLock))
upsertLockWithPutEvent(ctx, t, srv, roleLock)

_, err = srv.Authorizer.Authorize(context.WithValue(ctx, ContextUser, localUser))
Expand Down
11 changes: 11 additions & 0 deletions lib/services/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func LockTargetsFromTLSIdentity(id tlsca.Identity) []types.LockTarget {
if id.MFAVerified != "" {
lockTargets = append(lockTargets, types.LockTarget{MFADevice: id.MFAVerified})
}
lockTargets = append(lockTargets, AccessRequestsToLockTargets(id.ActiveRequests)...)
return lockTargets
}

Expand All @@ -62,6 +63,16 @@ func RolesToLockTargets(roles []string) []types.LockTarget {
return lockTargets
}

// AccessRequestsToLockTargets converts a list of access requests to a list of
// LockTargets (one LockTarget per access request)
func AccessRequestsToLockTargets(accessRequests []string) []types.LockTarget {
lockTargets := make([]types.LockTarget, 0, len(accessRequests))
for _, accessRequest := range accessRequests {
lockTargets = append(lockTargets, types.LockTarget{AccessRequest: accessRequest})
}
return lockTargets
}

// UnmarshalLock unmarshals the Lock resource from JSON.
func UnmarshalLock(bytes []byte, opts ...MarshalOption) (types.Lock, error) {
if len(bytes) == 0 {
Expand Down
13 changes: 10 additions & 3 deletions lib/srv/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -925,12 +925,19 @@ func ComputeLockTargets(s Server, id IdentityContext) ([]types.LockTarget, error
if err != nil {
return nil, trace.Wrap(err)
}
roleTargets := services.RolesToLockTargets(apiutils.Deduplicate(append(id.RoleSet.RoleNames(), id.UnmappedRoles...)))
return append([]types.LockTarget{
lockTargets := []types.LockTarget{
{User: id.TeleportUser},
{Login: id.Login},
{Node: s.HostUUID()},
{Node: auth.HostFQDN(s.HostUUID(), clusterName.GetClusterName())},
{MFADevice: id.Certificate.Extensions[teleport.CertExtensionMFAVerified]},
}, roleTargets...), nil
}
roles := apiutils.Deduplicate(append(id.RoleSet.RoleNames(), id.UnmappedRoles...))
lockTargets = append(lockTargets,
services.RolesToLockTargets(roles)...,
)
lockTargets = append(lockTargets,
services.AccessRequestsToLockTargets(id.ActiveRequests)...,
)
return lockTargets, nil
}
1 change: 1 addition & 0 deletions tool/tctl/common/lock_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (c *LockCommand) Initialize(app *kingpin.Application, config *service.Confi
c.mainCmd.Flag("login", "Name of a local UNIX user to disable.").StringVar(&c.spec.Target.Login)
c.mainCmd.Flag("node", "UUID of a Teleport node to disable.").StringVar(&c.spec.Target.Node)
c.mainCmd.Flag("mfa-device", "UUID of a user MFA device to disable.").StringVar(&c.spec.Target.MFADevice)
c.mainCmd.Flag("access-request", "UUID of an access request to disable.").StringVar(&c.spec.Target.AccessRequest)
c.mainCmd.Flag("message", "Message to display to locked-out users.").StringVar(&c.spec.Message)
c.mainCmd.Flag("expires", "Time point (RFC3339) when the lock expires.").StringVar(&c.expires)
c.mainCmd.Flag("ttl", "Time duration after which the lock expires.").DurationVar(&c.ttl)
Expand Down

0 comments on commit 5addc89

Please sign in to comment.