Skip to content

Commit

Permalink
[v17] Adds Roles for IC resource access requests
Browse files Browse the repository at this point in the history
Backports #49565

---------
Co-authored-by: Roman Tkachenko <[email protected]>
  • Loading branch information
tcsc committed Dec 16, 2024
1 parent 087d926 commit ef2349b
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 19 deletions.
13 changes: 13 additions & 0 deletions api/types/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ type Role interface {
// GetIdentityCenterAccountAssignments fetches the allow or deny Account
// Assignments for the role
GetIdentityCenterAccountAssignments(RoleConditionType) []IdentityCenterAccountAssignment
// GetIdentityCenterAccountAssignments sets the allow or deny Account
// Assignments for the role
SetIdentityCenterAccountAssignments(RoleConditionType, []IdentityCenterAccountAssignment)
}

// NewRole constructs new standard V7 role.
Expand Down Expand Up @@ -2068,6 +2071,16 @@ func (r *RoleV6) GetIdentityCenterAccountAssignments(rct RoleConditionType) []Id
return r.Spec.Deny.AccountAssignments
}

// SetIdentityCenterAccountAssignments sets the allow or deny Identity Center
// Account Assignments for the role
func (r *RoleV6) SetIdentityCenterAccountAssignments(rct RoleConditionType, assignments []IdentityCenterAccountAssignment) {
cond := &r.Spec.Deny
if rct == Allow {
cond = &r.Spec.Allow
}
cond.AccountAssignments = assignments
}

// LabelMatcherKinds is the complete list of resource kinds that support label
// matchers.
var LabelMatcherKinds = []string{
Expand Down
5 changes: 5 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,11 @@ const (
// access to Okta resources. This will be used by the Okta requester role to
// search for Okta resources.
SystemOktaAccessRoleName = "okta-access"

// SystemIdentityCenterAccessRoleName specifies the name of a system role
// that grants a user access to AWS Identity Center resources via
// Access Requests.
SystemIdentityCenterAccessRoleName = "aws-ic-access"
)

var PresetRoles = []string{PresetEditorRoleName, PresetAccessRoleName, PresetAuditorRoleName}
Expand Down
1 change: 1 addition & 0 deletions lib/auth/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -1031,6 +1031,7 @@ func GetPresetRoles() []types.Role {
services.NewSystemOktaAccessRole(),
services.NewSystemOktaRequesterRole(),
services.NewPresetTerraformProviderRole(),
services.NewSystemIdentityCenterAccessRole(),
}

// Certain `New$FooRole()` functions will return a nil role if the
Expand Down
1 change: 1 addition & 0 deletions lib/auth/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,7 @@ func TestPresets(t *testing.T) {
enterpriseSystemRoleNames := []string{
teleport.SystemAutomaticAccessApprovalRoleName,
teleport.SystemOktaAccessRoleName,
teleport.SystemIdentityCenterAccessRoleName,
}

enterpriseUsers := []types.User{
Expand Down
132 changes: 122 additions & 10 deletions lib/services/presets.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ import (
"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/common"
apiutils "github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/utils"
)

// NewSystemAutomaticAccessApproverRole creates a new Role that is allowed to
Expand Down Expand Up @@ -577,6 +579,32 @@ func NewSystemOktaRequesterRole() types.Role {
return role
}

// NewSystemIdentityCenterAccessRole creates a role that allows access to AWS
// IdentityCenter resources via Access Requests
func NewSystemIdentityCenterAccessRole() types.Role {
if modules.GetModules().BuildType() != modules.BuildEnterprise {
return nil
}
return &types.RoleV6{
Kind: types.KindRole,
Version: types.V7,
Metadata: types.Metadata{
Name: teleport.SystemIdentityCenterAccessRoleName,
Namespace: apidefaults.Namespace,
Description: "Access AWS IAM Identity Center resources",
Labels: map[string]string{
types.TeleportInternalResourceType: types.SystemResource,
types.OriginLabel: common.OriginAWSIdentityCenter,
},
},
Spec: types.RoleSpecV6{
Allow: types.RoleConditions{
AccountAssignments: defaultAllowAccountAssignments(true)[teleport.SystemIdentityCenterAccessRoleName],
},
},
}
}

// NewPresetTerraformProviderRole returns a new pre-defined role for the Teleport Terraform provider.
// This role can edit any Terraform-supported resource.
func NewPresetTerraformProviderRole() types.Role {
Expand Down Expand Up @@ -716,6 +744,7 @@ func defaultAllowAccessRequestConditions(enterprise bool) map[string]*types.Acce
SearchAsRoles: []string{
teleport.PresetAccessRoleName,
teleport.PresetGroupAccessRoleName,
teleport.SystemIdentityCenterAccessRoleName,
},
},
teleport.SystemOktaRequesterRoleName: {
Expand All @@ -739,10 +768,12 @@ func defaultAllowAccessReviewConditions(enterprise bool) map[string]*types.Acces
PreviewAsRoles: []string{
teleport.PresetAccessRoleName,
teleport.PresetGroupAccessRoleName,
teleport.SystemIdentityCenterAccessRoleName,
},
Roles: []string{
teleport.PresetAccessRoleName,
teleport.PresetGroupAccessRoleName,
teleport.SystemIdentityCenterAccessRoleName,
},
},
}
Expand All @@ -751,6 +782,21 @@ func defaultAllowAccessReviewConditions(enterprise bool) map[string]*types.Acces
return map[string]*types.AccessReviewConditions{}
}

func defaultAllowAccountAssignments(enterprise bool) map[string][]types.IdentityCenterAccountAssignment {
if enterprise {
return map[string][]types.IdentityCenterAccountAssignment{
teleport.SystemIdentityCenterAccessRoleName: {
{
Account: types.Wildcard,
PermissionSet: types.Wildcard,
},
},
}
}

return map[string][]types.IdentityCenterAccountAssignment{}
}

// AddRoleDefaults adds default role attributes to a preset role.
// Only attributes whose resources are not already defined (either allowing or denying) are added.
func AddRoleDefaults(role types.Role) (types.Role, error) {
Expand Down Expand Up @@ -852,18 +898,18 @@ func AddRoleDefaults(role types.Role) (types.Role, error) {
}
}

if role.GetAccessRequestConditions(types.Allow).IsEmpty() {
arc := defaultAllowAccessRequestConditions(enterprise)[role.GetName()]
if arc != nil {
role.SetAccessRequestConditions(types.Allow, *arc)
changed = true
}
if roleUpdated := applyAccessRequestConditionDefaults(role, enterprise); roleUpdated {
changed = true
}

if role.GetAccessReviewConditions(types.Allow).IsEmpty() {
arc := defaultAllowAccessReviewConditions(enterprise)[role.GetName()]
if arc != nil {
role.SetAccessReviewConditions(types.Allow, *arc)
if roleUpdated := applyAccessReviewConditionDefaults(role, enterprise); roleUpdated {
changed = true
}

if len(role.GetIdentityCenterAccountAssignments(types.Allow)) == 0 {
assignments := defaultAllowAccountAssignments(enterprise)[role.GetName()]
if assignments != nil {
role.SetIdentityCenterAccountAssignments(types.Allow, assignments)
changed = true
}
}
Expand All @@ -875,6 +921,72 @@ func AddRoleDefaults(role types.Role) (types.Role, error) {
return role, nil
}

func mergeStrings(dst, src []string) (merged []string, changed bool) {
items := utils.NewSet[string](dst...)
items.Add(src...)
if len(items) == len(dst) {
return dst, false
}
dst = items.Elements()
slices.Sort(dst)
return dst, true
}

func applyAccessRequestConditionDefaults(role types.Role, enterprise bool) bool {
defaults := defaultAllowAccessRequestConditions(enterprise)[role.GetName()]
if defaults == nil {
return false
}

target := role.GetAccessRequestConditions(types.Allow)
changed := false
if target.IsEmpty() {
target = *defaults
changed = true
} else {
var rolesUpdated bool

target.Roles, rolesUpdated = mergeStrings(target.Roles, defaults.Roles)
changed = changed || rolesUpdated

target.SearchAsRoles, rolesUpdated = mergeStrings(target.SearchAsRoles, defaults.SearchAsRoles)
changed = changed || rolesUpdated
}

if changed {
role.SetAccessRequestConditions(types.Allow, target)
}

return changed
}

func applyAccessReviewConditionDefaults(role types.Role, enterprise bool) bool {
defaults := defaultAllowAccessReviewConditions(enterprise)[role.GetName()]
if defaults == nil {
return false
}

target := role.GetAccessReviewConditions(types.Allow)
changed := false
if target.IsEmpty() {
target = *defaults
changed = true
} else {
var rolesUpdated bool

target.Roles, rolesUpdated = mergeStrings(target.Roles, defaults.Roles)
changed = changed || rolesUpdated

target.PreviewAsRoles, rolesUpdated = mergeStrings(target.PreviewAsRoles, defaults.PreviewAsRoles)
changed = changed || rolesUpdated
}

if changed {
role.SetAccessReviewConditions(types.Allow, target)
}
return changed
}

func labelMatchersUnset(role types.Role, kind string) (bool, error) {
for _, cond := range []types.RoleConditionType{types.Allow, types.Deny} {
labelMatchers, err := role.GetLabelMatchers(cond, kind)
Expand Down
96 changes: 87 additions & 9 deletions lib/services/presets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,13 +349,41 @@ func TestAddRoleDefaults(t *testing.T) {
Spec: types.RoleSpecV6{
Allow: types.RoleConditions{
ReviewRequests: &types.AccessReviewConditions{
Roles: []string{"some-role"},
Roles: []string{"some-role"},
PreviewAsRoles: []string{"preview-role"},
},
},
},
},
enterprise: true,
expectedErr: require.NoError,
reviewNotEmpty: true,
expected: &types.RoleV6{
Metadata: types.Metadata{
Name: teleport.PresetReviewerRoleName,
Labels: map[string]string{
types.TeleportInternalResourceType: types.PresetResource,
},
},
Spec: types.RoleSpecV6{
Allow: types.RoleConditions{
ReviewRequests: &types.AccessReviewConditions{
Roles: []string{
teleport.PresetAccessRoleName,
teleport.SystemIdentityCenterAccessRoleName,
teleport.PresetGroupAccessRoleName,
"some-role",
},
PreviewAsRoles: []string{
teleport.PresetAccessRoleName,
teleport.SystemIdentityCenterAccessRoleName,
teleport.PresetGroupAccessRoleName,
"preview-role",
},
},
},
},
},
enterprise: true,
expectedErr: noChange,
},
{
name: "requester (not enterprise)",
Expand Down Expand Up @@ -411,6 +439,25 @@ func TestAddRoleDefaults(t *testing.T) {
{
name: "requester (enterprise, existing requests)",
role: &types.RoleV6{
Metadata: types.Metadata{
Name: teleport.PresetRequesterRoleName,
Labels: map[string]string{
types.TeleportInternalResourceType: types.PresetResource,
},
},
Spec: types.RoleSpecV6{
Allow: types.RoleConditions{
Request: &types.AccessRequestConditions{
Roles: []string{"some-role"},
SearchAsRoles: []string{"search-as-role"},
},
},
},
},
enterprise: true,
expectedErr: require.NoError,
accessRequestsNotEmpty: true,
expected: &types.RoleV6{
Metadata: types.Metadata{
Name: teleport.PresetRequesterRoleName,
Labels: map[string]string{
Expand All @@ -421,12 +468,16 @@ func TestAddRoleDefaults(t *testing.T) {
Allow: types.RoleConditions{
Request: &types.AccessRequestConditions{
Roles: []string{"some-role"},
SearchAsRoles: []string{
teleport.PresetAccessRoleName,
teleport.SystemIdentityCenterAccessRoleName,
teleport.PresetGroupAccessRoleName,
"search-as-role",
},
},
},
},
},
enterprise: true,
expectedErr: noChange,
},
{
name: "okta resources (not enterprise)",
Expand Down Expand Up @@ -555,8 +606,28 @@ func TestAddRoleDefaults(t *testing.T) {
},
},
},
enterprise: true,
expectedErr: noChange,
enterprise: true,
expectedErr: require.NoError,
accessRequestsNotEmpty: true,
expected: &types.RoleV6{
Metadata: types.Metadata{
Name: teleport.SystemOktaRequesterRoleName,
Labels: map[string]string{
types.TeleportInternalResourceType: types.SystemResource,
types.OriginLabel: types.OriginOkta,
},
},
Spec: types.RoleSpecV6{
Allow: types.RoleConditions{
Request: &types.AccessRequestConditions{
Roles: []string{"some-role"},
SearchAsRoles: []string{
teleport.SystemOktaAccessRoleName,
},
},
},
},
},
},
}

Expand All @@ -574,8 +645,15 @@ func TestAddRoleDefaults(t *testing.T) {
require.Empty(t, cmp.Diff(role, test.expected))

if test.expected != nil {
require.Equal(t, test.reviewNotEmpty, !role.GetAccessReviewConditions(types.Allow).IsEmpty())
require.Equal(t, test.accessRequestsNotEmpty, !role.GetAccessRequestConditions(types.Allow).IsEmpty())
require.Equal(t, test.reviewNotEmpty,
!role.GetAccessReviewConditions(types.Allow).IsEmpty(),
"Expected populated Access Review Conditions (%t)",
test.reviewNotEmpty)

require.Equal(t, test.accessRequestsNotEmpty,
!role.GetAccessRequestConditions(types.Allow).IsEmpty(),
"Expected populated Access Request Conditions (%t)",
test.accessRequestsNotEmpty)
}
})
}
Expand Down

0 comments on commit ef2349b

Please sign in to comment.