diff --git a/docs/config/plugins/github-com-jenkins-x-lighthouse-pkg-plugins.md b/docs/config/plugins/github-com-jenkins-x-lighthouse-pkg-plugins.md index 7436d5cc7..d3f8f082e 100644 --- a/docs/config/plugins/github-com-jenkins-x-lighthouse-pkg-plugins.md +++ b/docs/config/plugins/github-com-jenkins-x-lighthouse-pkg-plugins.md @@ -206,6 +206,7 @@ Trigger specifies a configuration for a single trigger.

The configura |---|---|---|---| | `repos` | []string | No | Repos is either of the form org/repos or just org. | | `trusted_org` | string | No | TrustedOrg is the org whose members' PRs will be automatically built
for PRs to the above repos. The default is the PR's org. | +| `trusted_apps` | []string | No | TrustedApps is the explicit list of GitHub apps whose PRs will be automatically
considered as trusted. The list should contain usernames of each GitHub App without [bot] suffix.
By default, trigger will ignore this list. | | `join_org_url` | string | No | JoinOrgURL is a link that redirects users to a location where they
should be able to read more about joining the organization in order
to become trusted members. Defaults to the Github link of TrustedOrg. | | `only_org_members` | bool | No | OnlyOrgMembers requires PRs and/or /ok-to-test comments to come from org members.
By default, trigger also include repo collaborators. | | `ignore_ok_to_test` | bool | No | IgnoreOkToTest makes trigger ignore /ok-to-test comments.
This is a security mitigation to only allow testing from trusted users. | diff --git a/docs/plugins/Plugins config.md b/docs/plugins/Plugins config.md index a82eb07e1..359e7e0e5 100644 --- a/docs/plugins/Plugins config.md +++ b/docs/plugins/Plugins config.md @@ -211,6 +211,7 @@ Trigger specifies a configuration for a single trigger.

The configura |---|---|---|---|---| | Repos | `repos` | []string | No | Repos is either of the form org/repos or just org. | | TrustedOrg | `trusted_org` | string | No | TrustedOrg is the org whose members' PRs will be automatically built
for PRs to the above repos. The default is the PR's org. | +| TrustedApps | `trusted_apps` | []string | No | TrustedApps is the explicit list of GitHub apps whose PRs will be automatically
considered as trusted. The list should contain usernames of each GitHub App without [bot] suffix.
By default, trigger will ignore this list. | | JoinOrgURL | `join_org_url` | string | No | JoinOrgURL is a link that redirects users to a location where they
should be able to read more about joining the organization in order
to become trusted members. Defaults to the Github link of TrustedOrg. | | OnlyOrgMembers | `only_org_members` | bool | No | OnlyOrgMembers requires PRs and/or /ok-to-test comments to come from org members.
By default, trigger also include repo collaborators. | | IgnoreOkToTest | `ignore_ok_to_test` | bool | No | IgnoreOkToTest makes trigger ignore /ok-to-test comments.
This is a security mitigation to only allow testing from trusted users. | diff --git a/pkg/plugins/config.go b/pkg/plugins/config.go index e0529c1ae..e30674996 100644 --- a/pkg/plugins/config.go +++ b/pkg/plugins/config.go @@ -270,6 +270,10 @@ type Trigger struct { // TrustedOrg is the org whose members' PRs will be automatically built // for PRs to the above repos. The default is the PR's org. TrustedOrg string `json:"trusted_org,omitempty"` + // TrustedApps is the explicit list of GitHub apps whose PRs will be automatically + // considered as trusted. The list should contain usernames of each GitHub App without [bot] suffix. + // By default, trigger will ignore this list. + TrustedApps []string `json:"trusted_apps,omitempty"` // JoinOrgURL is a link that redirects users to a location where they // should be able to read more about joining the organization in order // to become trusted members. Defaults to the Github link of TrustedOrg. diff --git a/pkg/plugins/trigger/trigger.go b/pkg/plugins/trigger/trigger.go index 330d06305..5d4151f6a 100644 --- a/pkg/plugins/trigger/trigger.go +++ b/pkg/plugins/trigger/trigger.go @@ -186,7 +186,7 @@ func handlePush(pc plugins.Agent, pe scm.PushHook) error { // TrustedUser returns true if user is trusted in repo. // -// Trusted users are either repo collaborators, org members or trusted org members. +// Trusted users are either repo collaborators, org members, trusted org members or trusted Github Apps. // Whether repo collaborators and/or a second org is trusted is configured by trigger. func TrustedUser(spc trustedUserClient, trigger *plugins.Trigger, user, org, repo string) (bool, error) { botUser, err := spc.BotName() @@ -214,6 +214,14 @@ func TrustedUser(spc trustedUserClient, trigger *plugins.Trigger, user, org, rep return true, nil } + // Determine if user is on trusted_apps list. + // This allows automatic tests execution for GitHub automations that cannot be added as collaborators. + for _, trustedApp := range trigger.TrustedApps { + if tUser := strings.TrimSuffix(user, "[bot]"); tUser == trustedApp { + return true, nil + } + } + // Determine if there is a second org to check if trigger.TrustedOrg == "" || trigger.TrustedOrg == org { return false, nil // No trusted org and/or it is the same diff --git a/pkg/plugins/trigger/trigger_test.go b/pkg/plugins/trigger/trigger_test.go index 2e0489f02..f88851b6d 100644 --- a/pkg/plugins/trigger/trigger_test.go +++ b/pkg/plugins/trigger/trigger_test.go @@ -515,3 +515,128 @@ func TestValidateContextOverlap(t *testing.T) { }) } } + +func TestTrustedUser(t *testing.T) { + var testcases = []struct { + name string + + onlyOrgMembers bool + trustedApps []string + trustedOrg string + + user string + org string + repo string + + expectedTrusted bool + }{ + { + name: "user is member of trusted org", + onlyOrgMembers: false, + user: "test", + org: "kubernetes", + repo: "kubernetes", + expectedTrusted: true, + }, + { + name: "user is member of trusted org (only org members enabled)", + onlyOrgMembers: true, + user: "test", + org: "kubernetes", + repo: "kubernetes", + expectedTrusted: true, + }, + { + name: "user is collaborator", + onlyOrgMembers: false, + user: "test-collaborator", + org: "kubernetes", + repo: "kubernetes", + expectedTrusted: true, + }, + { + name: "user is collaborator (only org members enabled)", + onlyOrgMembers: true, + user: "test-collaborator", + org: "kubernetes", + repo: "kubernetes", + expectedTrusted: false, + }, + { + name: "user is trusted org member", + onlyOrgMembers: false, + trustedOrg: "kubernetes", + user: "test", + org: "kubernetes-sigs", + repo: "test", + expectedTrusted: true, + }, + { + name: "user is not org member", + onlyOrgMembers: false, + user: "test-2", + org: "kubernetes", + repo: "kubernetes", + expectedTrusted: false, + }, + { + name: "user is not org member or trusted org member", + onlyOrgMembers: false, + trustedOrg: "kubernetes-sigs", + user: "test-2", + org: "kubernetes", + repo: "kubernetes", + expectedTrusted: false, + }, + { + name: "user is not org member or trusted org member, onlyOrgMembers true", + onlyOrgMembers: true, + trustedOrg: "kubernetes-sigs", + user: "test-2", + org: "kubernetes", + repo: "kubernetes", + expectedTrusted: false, + }, + { + name: "Self as bot is trusted", + user: "k8s-ci-robot", + expectedTrusted: true, + }, + { + name: "github-app[bot] is in trusted list", + user: "github-app[bot]", + trustedApps: []string{"github-app"}, + expectedTrusted: true, + }, + { + name: "github-app[bot] is not in trusted list", + user: "github-app[bot]", + trustedApps: []string{"other-app"}, + expectedTrusted: false, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + fakeSCMClient := fake2.SCMClient{} + fakeSCMClient.OrgMembers = map[string][]string{ + "kubernetes": {"test"}, + } + fakeSCMClient.Collaborators = []string{"test-collaborator"} + + triggerPlugin := plugins.Trigger{ + TrustedOrg: tc.trustedOrg, + TrustedApps: tc.trustedApps, + OnlyOrgMembers: tc.onlyOrgMembers, + } + + trustedResponse, err := TrustedUser(&fakeSCMClient, &triggerPlugin, tc.user, tc.org, tc.repo) + if err != nil { + t.Errorf("For case %s, didn't expect error from TrustedUser: %v", tc.name, err) + } + if trustedResponse != tc.expectedTrusted { + t.Errorf("For case %s, expect trusted: %v, but got: %v", tc.name, tc.expectedTrusted, trustedResponse) + } + }) + } +}