From bc84c6a6d59518b4f65d8c713b016489655c4fcb Mon Sep 17 00:00:00 2001 From: "STeve (Xin) Huang" Date: Wed, 22 Jan 2025 10:32:51 -0500 Subject: [PATCH] GitHub proxy: role presets and user traits (#51299) * 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 20f7257413cc4..301ea5ee733c0 100644 --- a/constants.go +++ b/constants.go @@ -642,6 +642,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 e4ab09c146928..1cf2a918a388d 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -197,6 +197,7 @@ func NewPresetEditorRole() types.Role { types.NewRule(types.KindAutoUpdateVersion, RW()), types.NewRule(types.KindAutoUpdateConfig, RW()), types.NewRule(types.KindAutoUpdateAgentRollout, RO()), + types.NewRule(types.KindGitServer, RW()), }, }, }, @@ -253,6 +254,9 @@ func NewPresetAccessRole() types.Role { Verbs: []string{types.Wildcard}, }, }, + GitHubPermissions: []types.GitHubPermission{{ + Organizations: []string{teleport.TraitInternalGitHubOrgs}, + }}, Rules: []types.Rule{ types.NewRule(types.KindEvent, RO()), { @@ -693,6 +697,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()), }, }, }, @@ -955,6 +960,16 @@ func AddRoleDefaults(ctx context.Context, 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") } @@ -1066,3 +1081,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 4252208c37bb4..0c33b5866db11 100644 --- a/lib/services/presets_test.go +++ b/lib/services/presets_test.go @@ -139,6 +139,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], + }}, }, }, }, @@ -171,6 +174,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], + }}, }, }, }, @@ -186,6 +192,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], + }}, }, }, }, @@ -202,6 +211,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], + }}, }, }, }, @@ -735,6 +747,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 d9691dc29ec27..458662acf5305 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 8feea55b4000c..b5fe8b1778f11 100644 --- a/lib/services/role_test.go +++ b/lib/services/role_test.go @@ -2960,6 +2960,8 @@ func TestApplyTraits(t *testing.T) { inSudoers []string outSudoers []string outKubeResources []types.KubernetesResource + inGitHubPermissions []types.GitHubPermission + outGitHubPermissions []types.GitHubPermission } tests := []struct { comment string @@ -3728,6 +3730,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) { @@ -3759,6 +3789,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, @@ -3780,6 +3811,7 @@ func TestApplyTraits(t *testing.T) { Impersonate: &tt.deny.inImpersonate, HostSudoers: tt.deny.outSudoers, KubernetesResources: tt.deny.inKubeResources, + GitHubPermissions: tt.deny.inGitHubPermissions, }, }, } @@ -3813,6 +3845,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', ]; /**