Skip to content

Commit

Permalink
Respect role max duration when request does not set it (#51467)
Browse files Browse the repository at this point in the history
* Respect role max duration when request does not set it

* Update lib/services/access_request_test.go

Co-authored-by: Pawel Kopiczko <[email protected]>

* Update lib/services/access_request.go

Co-authored-by: Pawel Kopiczko <[email protected]>

* Add test for max_duration when a requesting user has multiple roles

* Refactor max duration calculation to be clearer

* Fix typo

---------

Co-authored-by: Pawel Kopiczko <[email protected]>
  • Loading branch information
EdwardDowling and kopiczko authored Jan 31, 2025
1 parent af77efb commit 46b23ce
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 28 deletions.
42 changes: 23 additions & 19 deletions lib/services/access_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -1393,44 +1393,34 @@ func (v *RequestValidator) isReasonRequired(ctx context.Context, requestedRoles
func (m *RequestValidator) calculateMaxAccessDuration(req types.AccessRequest, sessionTTL time.Duration) (time.Duration, error) {
// Check if the maxDuration time is set.
maxDurationTime := req.GetMaxDuration()
if maxDurationTime.IsZero() {
return 0, nil
}

maxDuration := maxDurationTime.Sub(req.GetCreationTime())

// For dry run requests, use the maximum possible duration.
// This prevents the time drift that can occur as the value is set on the client side.
if req.GetDryRun() {
maxDuration = MaxAccessDuration
} else if maxDuration < 0 {
// maxDuration may end up < 0 even if maxDurationTime is set
} else if !maxDurationTime.IsZero() && maxDuration < 0 {
return 0, trace.BadParameter("invalid maxDuration: must be greater than creation time")
}

if maxDuration > MaxAccessDuration {
return 0, trace.BadParameter("max_duration must be less than or equal to %v", MaxAccessDuration)
}

minAdjDuration := maxDuration
var minAdjDuration time.Duration
// Adjust the expiration time if the max_duration value is set on the request role.
for _, roleName := range req.GetRoles() {
var maxDurationForRole time.Duration
for _, tms := range m.MaxDurationMatchers {
for _, matcher := range tms.Matchers {
if matcher.Match(roleName) {
if tms.MaxDuration > maxDurationForRole {
maxDurationForRole = tms.MaxDuration
}
}
}
}

if maxDurationForRole < minAdjDuration {
maxDurationForRole := m.maxDurationForRole(roleName)
if minAdjDuration == 0 || maxDurationForRole < minAdjDuration {
minAdjDuration = maxDurationForRole
}
}
if !maxDurationTime.IsZero() && maxDuration < minAdjDuration {
minAdjDuration = maxDuration
}

// minAdjDuration can end up being 0, if any role does not have
// minAdjDuration can end up being 0, if no role has a
// field `max_duration` defined.
// In this case, return the smaller value between the sessionTTL
// and the requested max duration.
Expand All @@ -1441,6 +1431,20 @@ func (m *RequestValidator) calculateMaxAccessDuration(req types.AccessRequest, s
return minAdjDuration, nil
}

func (m *RequestValidator) maxDurationForRole(roleName string) time.Duration {
var maxDurationForRole time.Duration
for _, tms := range m.MaxDurationMatchers {
for _, matcher := range tms.Matchers {
if matcher.Match(roleName) {
if tms.MaxDuration > maxDurationForRole {
maxDurationForRole = tms.MaxDuration
}
}
}
}
return maxDurationForRole
}

// calculatePendingRequestTTL calculates the TTL of the Access Request (how long it will await
// approval). request TTL is capped to the smaller value between the const requestTTL and the
// access request access expiry.
Expand Down
40 changes: 31 additions & 9 deletions lib/services/access_request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2673,6 +2673,14 @@ func TestValidate_RequestedMaxDuration(t *testing.T) {
},
},
},
"shortMaxDurationReqRole3": {
condition: types.RoleConditions{
Request: &types.AccessRequestConditions{
Roles: []string{"requestedRole"},
MaxDuration: types.Duration(day),
},
},
},
}

// describes a collection of users with various roles
Expand All @@ -2681,6 +2689,7 @@ func TestValidate_RequestedMaxDuration(t *testing.T) {
"bob": {"defaultRole"},
"carol": {"shortMaxDurationReqRole", "shortMaxDurationReqRole2"},
"david": {"maxDurationReqRole"},
"erin": {"shortMaxDurationReqRole", "shortMaxDurationReqRole3", "maxDurationReqRole"},
}

defaultSessionTTL := 8 * time.Hour
Expand Down Expand Up @@ -2724,14 +2733,6 @@ func TestValidate_RequestedMaxDuration(t *testing.T) {
expectedSessionTTL: 8 * time.Hour,
dryRun: true,
},
{
desc: "role max_duration is ignored when requestedMaxDuration is not set",
requestor: "alice",
roles: []string{"requestedRole"}, // role max_duration capped to 3 days
expectedAccessDuration: 8 * time.Hour, // caps to defaultSessionTTL since requestedMaxDuration was not set
expectedPendingTTL: 8 * time.Hour,
expectedSessionTTL: 8 * time.Hour,
},
{
desc: "when role max_duration is not set: default to defaultSessionTTL when requestedMaxDuration is not set",
requestor: "bob",
Expand Down Expand Up @@ -2812,6 +2813,25 @@ func TestValidate_RequestedMaxDuration(t *testing.T) {
expectedPendingTTL: day,
expectedSessionTTL: 8 * time.Hour,
},
{
desc: "role max_duration is respected when requestedMaxDuration is not set",
requestor: "alice", // has shortMaxDurationReqRole role assigned
roles: []string{"requestedRole"}, // caps max_duration to 3 days
expectedAccessDuration: 3 * day,
expectedPendingTTL: 3 * day,
expectedSessionTTL: 8 * time.Hour,
},
{
desc: "multiple roles with different max durations assigned",
// erin has the maxDurationReqRole (7 days) role assigned
// along with 2 other roles with shorter durations
// shortMaxDurationReqRole (3 days) and shortMaxDurationReqRole3 (1 day)
requestor: "erin",
roles: []string{"requestedRole"}, // caps max_duration to 7 days
expectedAccessDuration: 7 * day,
expectedPendingTTL: 7 * day,
expectedSessionTTL: 8 * time.Hour,
},
}

for _, tt := range tts {
Expand All @@ -2832,7 +2852,9 @@ func TestValidate_RequestedMaxDuration(t *testing.T) {
require.NoError(t, err)

req.SetCreationTime(now)
req.SetMaxDuration(now.Add(tt.requestedMaxDuration))
if tt.requestedMaxDuration != 0 {
req.SetMaxDuration(now.Add(tt.requestedMaxDuration))
}
req.SetDryRun(tt.dryRun)

require.NoError(t, validator.Validate(context.Background(), req, identity))
Expand Down

0 comments on commit 46b23ce

Please sign in to comment.