From dd09b26ff82a19e9d0d010c7e41eb9934c7cf2d3 Mon Sep 17 00:00:00 2001 From: "STeve (Xin) Huang" Date: Wed, 22 Jan 2025 14:33:41 -0500 Subject: [PATCH] GitHub proxy: role presets and user traits (#51299) (#51369) * GitHub proxy: role presets and user traits * fix ut --- api/constants/constants.go | 4 +++ constants.go | 4 +++ lib/services/presets.go | 21 ++++++++++++ lib/services/presets_test.go | 13 ++++++++ lib/services/role.go | 9 ++++- lib/services/role_test.go | 33 +++++++++++++++++++ .../src/Users/UserAddEdit/TraitsEditor.tsx | 1 + 7 files changed, 84 insertions(+), 1 deletion(-) diff --git a/api/constants/constants.go b/api/constants/constants.go index 9f66302da597d..420df3992cdb6 100644 --- a/api/constants/constants.go +++ b/api/constants/constants.go @@ -405,6 +405,10 @@ const ( // TraitHostUserGID is the name of the variable used to specify // the GID to create host user account with. TraitHostUserGID = "host_user_gid" + + // TraitGitHubOrgs is the name of the variable to specify the GitHub + // organizations for GitHub integration. + TraitGitHubOrgs = "github_orgs" ) const ( diff --git a/constants.go b/constants.go index 908267c12ce53..46b1c9fcf8a85 100644 --- a/constants.go +++ b/constants.go @@ -639,6 +639,10 @@ const ( // TraitInternalJWTVariable is the variable used to store JWT token for // app sessions. TraitInternalJWTVariable = "{{internal.jwt}}" + + // TraitInternalGitHubOrgs is the variable used to store allowed GitHub + // organizations for GitHub integrations. + TraitInternalGitHubOrgs = "{{internal.github_orgs}}" ) // SCP is Secure Copy. diff --git a/lib/services/presets.go b/lib/services/presets.go index b04b43ce08112..586a66e67e73f 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -195,6 +195,7 @@ func NewPresetEditorRole() types.Role { types.NewRule(types.KindWorkloadIdentity, RW()), types.NewRule(types.KindAutoUpdateVersion, RW()), types.NewRule(types.KindAutoUpdateConfig, RW()), + types.NewRule(types.KindGitServer, RW()), }, }, }, @@ -251,6 +252,9 @@ func NewPresetAccessRole() types.Role { Verbs: []string{types.Wildcard}, }, }, + GitHubPermissions: []types.GitHubPermission{{ + Organizations: []string{teleport.TraitInternalGitHubOrgs}, + }}, Rules: []types.Rule{ types.NewRule(types.KindEvent, RO()), { @@ -691,6 +695,7 @@ func NewPresetTerraformProviderRole() types.Role { types.NewRule(types.KindDynamicWindowsDesktop, RW()), types.NewRule(types.KindStaticHostUser, RW()), types.NewRule(types.KindWorkloadIdentity, RW()), + types.NewRule(types.KindGitServer, RW()), }, }, }, @@ -950,6 +955,16 @@ func AddRoleDefaults(role types.Role) (types.Role, error) { } } + // GitHub permissions. + if len(role.GetGitHubPermissions(types.Allow)) == 0 { + if githubOrgs := defaultGitHubOrgs()[role.GetName()]; len(githubOrgs) > 0 { + role.SetGitHubPermissions(types.Allow, []types.GitHubPermission{{ + Organizations: githubOrgs, + }}) + changed = true + } + } + if !changed { return nil, trace.AlreadyExists("no change") } @@ -1061,3 +1076,9 @@ func updateAllowLabels(role types.Role, kind string, defaultLabels types.Labels) return changed, nil } + +func defaultGitHubOrgs() map[string][]string { + return map[string][]string{ + teleport.PresetAccessRoleName: []string{teleport.TraitInternalGitHubOrgs}, + } +} diff --git a/lib/services/presets_test.go b/lib/services/presets_test.go index 83d957aa4e5ab..6eec2f3a821d3 100644 --- a/lib/services/presets_test.go +++ b/lib/services/presets_test.go @@ -138,6 +138,9 @@ func TestAddRoleDefaults(t *testing.T) { DatabaseServiceLabels: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseServiceLabels, DatabaseRoles: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseRoles, Rules: NewPresetAccessRole().GetRules(types.Allow), + GitHubPermissions: []types.GitHubPermission{{ + Organizations: defaultGitHubOrgs()[teleport.PresetAccessRoleName], + }}, }, }, }, @@ -170,6 +173,9 @@ func TestAddRoleDefaults(t *testing.T) { DatabaseServiceLabels: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseServiceLabels, DatabaseRoles: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseRoles, Rules: defaultAllowRules()[teleport.PresetAccessRoleName], + GitHubPermissions: []types.GitHubPermission{{ + Organizations: defaultGitHubOrgs()[teleport.PresetAccessRoleName], + }}, }, }, }, @@ -185,6 +191,9 @@ func TestAddRoleDefaults(t *testing.T) { DatabaseServiceLabels: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseServiceLabels, DatabaseRoles: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseRoles, Rules: defaultAllowRules()[teleport.PresetAccessRoleName], + GitHubPermissions: []types.GitHubPermission{{ + Organizations: defaultGitHubOrgs()[teleport.PresetAccessRoleName], + }}, }, }, }, @@ -201,6 +210,9 @@ func TestAddRoleDefaults(t *testing.T) { DatabaseServiceLabels: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseServiceLabels, DatabaseRoles: defaultAllowLabels(false)[teleport.PresetAccessRoleName].DatabaseRoles, Rules: defaultAllowRules()[teleport.PresetAccessRoleName], + GitHubPermissions: []types.GitHubPermission{{ + Organizations: defaultGitHubOrgs()[teleport.PresetAccessRoleName], + }}, }, }, }, @@ -734,6 +746,7 @@ func TestAddRoleDefaults(t *testing.T) { types.NewRule(types.KindDynamicWindowsDesktop, RW()), types.NewRule(types.KindStaticHostUser, RW()), types.NewRule(types.KindWorkloadIdentity, RW()), + types.NewRule(types.KindGitServer, RW()), }, }, }, diff --git a/lib/services/role.go b/lib/services/role.go index e08b3a4e84d6a..070d495acb74d 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -511,6 +511,12 @@ func ApplyTraits(r types.Role, traits map[string][]string) (types.Role, error) { outDbRoles := applyValueTraitsSlice(inDbRoles, traits, "database role") r.SetDatabaseRoles(condition, apiutils.Deduplicate(outDbRoles)) + githubPermissions := r.GetGitHubPermissions(condition) + for i, perm := range githubPermissions { + githubPermissions[i].Organizations = applyValueTraitsSlice(perm.Organizations, traits, "github organizations") + } + r.SetGitHubPermissions(condition, githubPermissions) + var out []types.KubernetesResource // we access the resources in the role using the role conditions // to avoid receiving the compatibility resources added in GetKubernetesResources @@ -677,7 +683,8 @@ func ApplyValueTraits(val string, traits map[string][]string) ([]string, error) constants.TraitKubeGroups, constants.TraitKubeUsers, constants.TraitDBNames, constants.TraitDBUsers, constants.TraitDBRoles, constants.TraitAWSRoleARNs, constants.TraitAzureIdentities, - constants.TraitGCPServiceAccounts, constants.TraitJWT: + constants.TraitGCPServiceAccounts, constants.TraitJWT, + constants.TraitGitHubOrgs: default: return trace.BadParameter("unsupported variable %q", name) } diff --git a/lib/services/role_test.go b/lib/services/role_test.go index 58befb8153985..527a34ccc3851 100644 --- a/lib/services/role_test.go +++ b/lib/services/role_test.go @@ -3007,6 +3007,8 @@ func TestApplyTraits(t *testing.T) { inSudoers []string outSudoers []string outKubeResources []types.KubernetesResource + inGitHubPermissions []types.GitHubPermission + outGitHubPermissions []types.GitHubPermission } tests := []struct { comment string @@ -3775,6 +3777,34 @@ func TestApplyTraits(t *testing.T) { }, }, }, + { + comment: "GitHub permissions in allow rule", + inTraits: map[string][]string{ + "github_orgs": {"my-org1", "my-org2"}, + }, + allow: rule{ + inGitHubPermissions: []types.GitHubPermission{{ + Organizations: []string{"{{internal.github_orgs}}"}, + }}, + outGitHubPermissions: []types.GitHubPermission{{ + Organizations: []string{"my-org1", "my-org2"}, + }}, + }, + }, + { + comment: "GitHub permissions in deny rule", + inTraits: map[string][]string{ + "orgs": {"my-org1", "my-org2"}, + }, + deny: rule{ + inGitHubPermissions: []types.GitHubPermission{{ + Organizations: []string{"{{external.orgs}}"}, + }}, + outGitHubPermissions: []types.GitHubPermission{{ + Organizations: []string{"my-org1", "my-org2"}, + }}, + }, + }, } for _, tt := range tests { t.Run(tt.comment, func(t *testing.T) { @@ -3806,6 +3836,7 @@ func TestApplyTraits(t *testing.T) { Impersonate: &tt.allow.inImpersonate, HostSudoers: tt.allow.inSudoers, KubernetesResources: tt.allow.inKubeResources, + GitHubPermissions: tt.allow.inGitHubPermissions, }, Deny: types.RoleConditions{ Logins: tt.deny.inLogins, @@ -3827,6 +3858,7 @@ func TestApplyTraits(t *testing.T) { Impersonate: &tt.deny.inImpersonate, HostSudoers: tt.deny.outSudoers, KubernetesResources: tt.deny.inKubeResources, + GitHubPermissions: tt.deny.inGitHubPermissions, }, }, } @@ -3860,6 +3892,7 @@ func TestApplyTraits(t *testing.T) { require.Equal(t, rule.spec.outImpersonate, outRole.GetImpersonateConditions(rule.condition)) require.Equal(t, rule.spec.outSudoers, outRole.GetHostSudoers(rule.condition)) require.Equal(t, rule.spec.outKubeResources, outRole.GetRoleConditions(rule.condition).KubernetesResources) + require.Equal(t, rule.spec.outGitHubPermissions, outRole.GetRoleConditions(rule.condition).GitHubPermissions) } }) } diff --git a/web/packages/teleport/src/Users/UserAddEdit/TraitsEditor.tsx b/web/packages/teleport/src/Users/UserAddEdit/TraitsEditor.tsx index 4179aeac18855..5237f6efa9872 100644 --- a/web/packages/teleport/src/Users/UserAddEdit/TraitsEditor.tsx +++ b/web/packages/teleport/src/Users/UserAddEdit/TraitsEditor.tsx @@ -42,6 +42,7 @@ const traitsPreset = [ 'kubernetes_users', 'logins', 'windows_logins', + 'github_orgs', ]; /**