diff --git a/constants.go b/constants.go index 9cd47a60f62e4..cab2025f770b2 100644 --- a/constants.go +++ b/constants.go @@ -668,6 +668,16 @@ const ( // during the setup of Connect My Computer. The prefix is followed by the name of the cluster // user. See teleterm.connectmycomputer.RoleSetup. ConnectMyComputerRoleNamePrefix = "connect-my-computer-" + + // SystemOktaRequesterRoleName is a name of a system role that allows + // for requesting access to Okta resources. This differs from the requester role + // in that it allows for requesting longer lived access. + SystemOktaRequesterRoleName = "okta-requester" + + // SystemOktaAccessRoleName is the name of the system role that allows + // access to Okta resources. This will be used by the Okta requester role to + // search for Okta resources. + SystemOktaAccessRoleName = "okta-access" ) var PresetRoles = []string{PresetEditorRoleName, PresetAccessRoleName, PresetAuditorRoleName} diff --git a/docs/pages/access-controls/access-requests/access-request-configuration.mdx b/docs/pages/access-controls/access-requests/access-request-configuration.mdx index e3d124c574aa9..7d781f72d1b50 100644 --- a/docs/pages/access-controls/access-requests/access-request-configuration.mdx +++ b/docs/pages/access-controls/access-requests/access-request-configuration.mdx @@ -230,7 +230,7 @@ spec: max_duration: 4d ``` -The value of `max_duration` can never exceed seven days. +The value of `max_duration` can never exceed fourteen days. ### How long Access Requests are valid diff --git a/lib/auth/init.go b/lib/auth/init.go index d4b18d28a0168..1977303f38d80 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -813,6 +813,8 @@ func GetPresetRoles() []types.Role { services.NewPresetDeviceAdminRole(), services.NewPresetDeviceEnrollRole(), services.NewPresetRequireTrustedDeviceRole(), + services.NewSystemOktaAccessRole(), + services.NewSystemOktaRequesterRole(), } // Certain `New$FooRole()` functions will return a nil role if the diff --git a/lib/auth/init_test.go b/lib/auth/init_test.go index a0929158a2df0..000d10369aed2 100644 --- a/lib/auth/init_test.go +++ b/lib/auth/init_test.go @@ -797,6 +797,8 @@ func TestPresets(t *testing.T) { enterpriseSystemRoleNames := []string{ teleport.SystemAutomaticAccessApprovalRoleName, + teleport.SystemOktaAccessRoleName, + teleport.SystemOktaRequesterRoleName, } enterpriseUsers := []types.User{ diff --git a/lib/services/access_request.go b/lib/services/access_request.go index d7fc32bced3d9..d42eb8e9c27b3 100644 --- a/lib/services/access_request.go +++ b/lib/services/access_request.go @@ -49,7 +49,7 @@ const day = 24 * time.Hour // maxAccessDuration is the maximum duration that an access request can be // granted for. -const maxAccessDuration = 7 * day +const maxAccessDuration = 14 * day // ValidateAccessRequest validates the AccessRequest and sets default values func ValidateAccessRequest(ar types.AccessRequest) error { @@ -369,7 +369,7 @@ func ValidateAccessPredicates(role types.Role) error { if maxDuration := role.GetAccessRequestConditions(types.Allow).MaxDuration; maxDuration.Duration() != 0 && maxDuration.Duration() > maxAccessDuration { - return trace.BadParameter("max access duration must be less or equal 7 days") + return trace.BadParameter("max access duration must be less than or equal to %v", maxAccessDuration) } return nil @@ -1230,9 +1230,8 @@ func (m *RequestValidator) calculateMaxAccessDuration(req types.AccessRequest) ( maxDuration := maxDurationTime.Sub(req.GetCreationTime()) - // For dry run requests, the max_duration is set to 7 days. + // 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. - // TODO(jakule): Replace with MaxAccessDuration that is a duration (5h, 4d etc), and not a point in time. if req.GetDryRun() { maxDuration = maxAccessDuration } else if maxDuration < 0 { @@ -1240,7 +1239,7 @@ func (m *RequestValidator) calculateMaxAccessDuration(req types.AccessRequest) ( } if maxDuration > maxAccessDuration { - return 0, trace.BadParameter("max_duration must be less or equal 7 days") + return 0, trace.BadParameter("max_duration must be less than or equal to %v", maxAccessDuration) } minAdjDuration := maxDuration diff --git a/lib/services/presets.go b/lib/services/presets.go index e8d256b909e99..575ae854148fb 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -484,6 +484,70 @@ func NewPresetRequireTrustedDeviceRole() types.Role { } } +// NewSystemOktaAccessRoleName is the system role that allows +// access to Okta resources. This will be used by the Okta requester role to +// search for Okta resources. +func NewSystemOktaAccessRole() types.Role { + if modules.GetModules().BuildType() != modules.BuildEnterprise { + return nil + } + + role := &types.RoleV6{ + Kind: types.KindRole, + Version: types.V7, + Metadata: types.Metadata{ + Name: teleport.SystemOktaAccessRoleName, + Namespace: apidefaults.Namespace, + Description: "Request Okta resources", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + AppLabels: types.Labels{ + types.OriginLabel: []string{types.OriginOkta}, + }, + GroupLabels: types.Labels{ + types.OriginLabel: []string{types.OriginOkta}, + }, + Rules: []types.Rule{ + types.NewRule(types.KindUserGroup, RO()), + }, + }, + }, + } + return role +} + +// NewSystemOktaRequesterRoleName is a system role that allows +// for requesting access to Okta resources. This differs from the requester role +// in that it allows for requesting longer lived access. +func NewSystemOktaRequesterRole() types.Role { + if modules.GetModules().BuildType() != modules.BuildEnterprise { + return nil + } + + role := &types.RoleV6{ + Kind: types.KindRole, + Version: types.V7, + Metadata: types.Metadata{ + Name: teleport.SystemOktaRequesterRoleName, + Namespace: apidefaults.Namespace, + Description: "Request Okta resources", + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Request: defaultAllowAccessRequestConditions(true)[teleport.SystemOktaRequesterRoleName], + }, + }, + } + return role +} + // bootstrapRoleMetadataLabels are metadata labels that will be applied to each role. // These are intended to add labels for older roles that didn't previously have them. func bootstrapRoleMetadataLabels() map[string]map[string]string { @@ -520,14 +584,25 @@ func defaultAllowRules() map[string][]types.Rule { // defaultAllowLabels has the Allow labels that should be set as default when they were not explicitly defined. // This is used to update existing builtin preset roles with new permissions during cluster upgrades. // The following Labels are supported: +// - AppLabels // - DatabaseServiceLabels (db_service_labels) -func defaultAllowLabels() map[string]types.RoleConditions { - return map[string]types.RoleConditions{ +// - GroupLabels +func defaultAllowLabels(enterprise bool) map[string]types.RoleConditions { + conditions := map[string]types.RoleConditions{ teleport.PresetAccessRoleName: { DatabaseServiceLabels: types.Labels{types.Wildcard: []string{types.Wildcard}}, DatabaseRoles: []string{teleport.TraitInternalDBRolesVariable}, }, } + + if enterprise { + conditions[teleport.SystemOktaAccessRoleName] = types.RoleConditions{ + AppLabels: types.Labels{types.OriginLabel: []string{types.OriginOkta}}, + GroupLabels: types.Labels{types.OriginLabel: []string{types.OriginOkta}}, + } + } + + return conditions } // defaultAllowAccessRequestConditions has the access request conditions that should be set as default when they were @@ -541,6 +616,12 @@ func defaultAllowAccessRequestConditions(enterprise bool) map[string]*types.Acce teleport.PresetGroupAccessRoleName, }, }, + teleport.SystemOktaRequesterRoleName: { + SearchAsRoles: []string{ + teleport.SystemOktaAccessRoleName, + }, + MaxDuration: types.NewDuration(maxAccessDuration), + }, } } @@ -597,7 +678,8 @@ func AddRoleDefaults(role types.Role) (types.Role, error) { // labels because we set the role metadata labels for roles that have been well established (access, // editor, auditor) that may not already have this label set, but we don't set it for newer roles // (group-access, reviewer, requester) that may have customer definitions. - if role.GetMetadata().Labels[types.TeleportInternalResourceType] != types.PresetResource { + resourceType := role.GetMetadata().Labels[types.TeleportInternalResourceType] + if resourceType != types.PresetResource && resourceType != types.SystemResource { return nil, trace.AlreadyExists("not modifying user created role") } @@ -619,16 +701,30 @@ func AddRoleDefaults(role types.Role) (types.Role, error) { } } + enterprise := modules.GetModules().BuildType() == modules.BuildEnterprise + // Labels - defaultLabels, ok := defaultAllowLabels()[role.GetName()] + defaultLabels, ok := defaultAllowLabels(enterprise)[role.GetName()] if ok { - if unset, err := labelMatchersUnset(role, types.KindDatabaseService); err != nil { - return nil, trace.Wrap(err) - } else if unset && len(defaultLabels.DatabaseServiceLabels) > 0 { - role.SetLabelMatchers(types.Allow, types.KindDatabaseService, types.LabelMatchers{ - Labels: defaultLabels.DatabaseServiceLabels, - }) - changed = true + for _, kind := range []string{ + types.KindApp, + types.KindDatabaseService, + types.KindUserGroup, + } { + var labels types.Labels + switch kind { + case types.KindApp: + labels = defaultLabels.AppLabels + case types.KindDatabaseService: + labels = defaultLabels.DatabaseServiceLabels + case types.KindUserGroup: + labels = defaultLabels.GroupLabels + } + labelsUpdated, err := updateAllowLabels(role, kind, labels) + if err != nil { + return nil, trace.Wrap(err) + } + changed = changed || labelsUpdated } if len(defaultLabels.DatabaseRoles) > 0 && len(role.GetDatabaseRoles(types.Allow)) == 0 { role.SetDatabaseRoles(types.Allow, defaultLabels.DatabaseRoles) @@ -636,8 +732,6 @@ func AddRoleDefaults(role types.Role) (types.Role, error) { } } - enterprise := modules.GetModules().BuildType() == modules.BuildEnterprise - if role.GetAccessRequestConditions(types.Allow).IsEmpty() { arc := defaultAllowAccessRequestConditions(enterprise)[role.GetName()] if arc != nil { @@ -685,3 +779,20 @@ func resourceBelongsToRules(rules []types.Rule, resources []string) bool { return false } + +func updateAllowLabels(role types.Role, kind string, defaultLabels types.Labels) (bool, error) { + unset, err := labelMatchersUnset(role, kind) + if err != nil { + return false, trace.Wrap(err) + } + + var changed bool + if unset && len(defaultLabels) > 0 { + role.SetLabelMatchers(types.Allow, kind, types.LabelMatchers{ + Labels: defaultLabels, + }) + changed = true + } + + return changed, nil +} diff --git a/lib/services/presets_test.go b/lib/services/presets_test.go index 93026d3e98916..28b9e3c3ab22a 100644 --- a/lib/services/presets_test.go +++ b/lib/services/presets_test.go @@ -134,8 +134,8 @@ func TestAddRoleDefaults(t *testing.T) { }, Spec: types.RoleSpecV6{ Allow: types.RoleConditions{ - DatabaseServiceLabels: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseServiceLabels, - DatabaseRoles: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseRoles, + DatabaseServiceLabels: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseServiceLabels, + DatabaseRoles: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseRoles, Rules: NewPresetAccessRole().GetRules(types.Allow), }, }, @@ -166,8 +166,8 @@ func TestAddRoleDefaults(t *testing.T) { }, Spec: types.RoleSpecV6{ Allow: types.RoleConditions{ - DatabaseServiceLabels: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseServiceLabels, - DatabaseRoles: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseRoles, + DatabaseServiceLabels: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseServiceLabels, + DatabaseRoles: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseRoles, Rules: defaultAllowRules()[teleport.PresetAccessRoleName], }, }, @@ -181,8 +181,8 @@ func TestAddRoleDefaults(t *testing.T) { }, Spec: types.RoleSpecV6{ Allow: types.RoleConditions{ - DatabaseServiceLabels: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseServiceLabels, - DatabaseRoles: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseRoles, + DatabaseServiceLabels: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseServiceLabels, + DatabaseRoles: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseRoles, Rules: defaultAllowRules()[teleport.PresetAccessRoleName], }, }, @@ -197,8 +197,8 @@ func TestAddRoleDefaults(t *testing.T) { }, Spec: types.RoleSpecV6{ Allow: types.RoleConditions{ - DatabaseServiceLabels: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseServiceLabels, - DatabaseRoles: defaultAllowLabels()[teleport.PresetAccessRoleName].DatabaseRoles, + DatabaseServiceLabels: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseServiceLabels, + DatabaseRoles: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseRoles, Rules: defaultAllowRules()[teleport.PresetAccessRoleName], }, }, @@ -428,6 +428,132 @@ func TestAddRoleDefaults(t *testing.T) { enterprise: true, expectedErr: noChange, }, + { + name: "okta resources (not enterprise)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaAccessRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + }, + expectedErr: noChange, + expected: nil, + }, + { + name: "okta resources (enterprise)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaAccessRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + }, + enterprise: true, + expectedErr: require.NoError, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaAccessRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + AppLabels: types.Labels{ + types.OriginLabel: []string{types.OriginOkta}, + }, + GroupLabels: types.Labels{ + types.OriginLabel: []string{types.OriginOkta}, + }, + }, + }, + }, + }, + { + name: "okta resources (enterprise, created by user)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaAccessRoleName, + }, + }, + enterprise: true, + expectedErr: notModifying, + expected: nil, + }, + { + name: "okta requester (not enterprise)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaRequesterRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + }, + expectedErr: noChange, + expected: nil, + }, + { + name: "okta requester (enterprise)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaRequesterRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + }, + enterprise: true, + expectedErr: require.NoError, + accessRequestsNotEmpty: true, + expected: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaRequesterRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Request: defaultAllowAccessRequestConditions(true)[teleport.SystemOktaRequesterRoleName], + }, + }, + }, + }, + { + name: "okta requester (enterprise, created by user)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaRequesterRoleName, + }, + }, + enterprise: true, + expectedErr: notModifying, + expected: nil, + }, + { + name: "okta requester (enterprise, existing requests)", + role: &types.RoleV6{ + Metadata: types.Metadata{ + Name: teleport.SystemOktaRequesterRoleName, + Labels: map[string]string{ + types.TeleportInternalResourceType: types.SystemResource, + }, + }, + Spec: types.RoleSpecV6{ + Allow: types.RoleConditions{ + Request: &types.AccessRequestConditions{ + Roles: []string{"some-role"}, + }, + }, + }, + }, + enterprise: true, + expectedErr: noChange, + }, } for _, test := range tests {