Skip to content

Commit d51c912

Browse files
zakiskpipelines-as-code[bot]
authored andcommitted
Fix GitOps Command Issue on Pushed Commit by Unautorized User
Issue: when an unautorized user sends GitOps comment on a pushed commit, PAC is triggering CI since access check is done only for pull_request event in verifyRepoAndUser func of controller. Solution: added a check for push event and Ops comment event type in verifyRepoAndUser func. https://issues.redhat.com/browse/SRVKP-7110 Signed-off-by: Zaki Shaikh <[email protected]>
1 parent 0390c98 commit d51c912

File tree

6 files changed

+54
-16
lines changed

6 files changed

+54
-16
lines changed

pkg/pipelineascode/match.go

+32-12
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,31 @@ is that what you want? make sure you use -n when generating the secret, eg: echo
132132
return repo, err
133133
}
134134

135+
// Verify whether the sender of the GitOps command (e.g., /test) has the appropriate permissions to
136+
// trigger CI on the repository, as any user is able to comment on a pushed commit in open-source repositories.
137+
if p.event.TriggerTarget == triggertype.Push && opscomments.IsAnyOpsEventType(p.event.EventType) {
138+
status := provider.StatusOpts{
139+
Status: CompletedStatus,
140+
Title: "Permission denied",
141+
Conclusion: failureConclusion,
142+
DetailsURL: p.event.URL,
143+
}
144+
if allowed, err := p.checkAccessOrErrror(ctx, repo, status, "by GitOps comment on push commit"); !allowed {
145+
return nil, err
146+
}
147+
}
148+
135149
// Check if the submitter is allowed to run this.
136150
// on push we don't need to check the policy since the user has pushed to the repo so it has access to it.
137151
// on comment we skip it for now, we are going to check later on
138152
if p.event.TriggerTarget != triggertype.Push && p.event.EventType != opscomments.NoOpsCommentEventType.String() {
139-
if allowed, err := p.checkAccessOrErrror(ctx, repo, "via "+p.event.TriggerTarget.String()); !allowed {
153+
status := provider.StatusOpts{
154+
Status: queuedStatus,
155+
Title: "Pending approval, waiting for an /ok-to-test",
156+
Conclusion: pendingConclusion,
157+
DetailsURL: p.event.URL,
158+
}
159+
if allowed, err := p.checkAccessOrErrror(ctx, repo, status, "via "+p.event.TriggerTarget.String()); !allowed {
140160
return nil, err
141161
}
142162
}
@@ -245,7 +265,13 @@ func (p *PacRun) getPipelineRunsFromRepo(ctx context.Context, repo *v1alpha1.Rep
245265
// if the event is a comment event, but we don't have any match from the keys.OnComment then do the ACL checks again
246266
// we skipped previously so we can get the match from the event to the pipelineruns
247267
if p.event.EventType == opscomments.NoOpsCommentEventType.String() || p.event.EventType == opscomments.OnCommentEventType.String() {
248-
if allowed, err := p.checkAccessOrErrror(ctx, repo, "by gitops comment"); !allowed {
268+
status := provider.StatusOpts{
269+
Status: queuedStatus,
270+
Title: "Pending approval, waiting for an /ok-to-test",
271+
Conclusion: pendingConclusion,
272+
DetailsURL: p.event.URL,
273+
}
274+
if allowed, err := p.checkAccessOrErrror(ctx, repo, status, "by GitOps comment on push commit"); !allowed {
249275
return nil, err
250276
}
251277
}
@@ -370,26 +396,20 @@ func (p *PacRun) checkNeedUpdate(_ string) (string, bool) {
370396
return "", false
371397
}
372398

373-
func (p *PacRun) checkAccessOrErrror(ctx context.Context, repo *v1alpha1.Repository, viamsg string) (bool, error) {
399+
func (p *PacRun) checkAccessOrErrror(ctx context.Context, repo *v1alpha1.Repository, status provider.StatusOpts, viamsg string) (bool, error) {
374400
allowed, err := p.vcx.IsAllowed(ctx, p.event)
375401
if err != nil {
376402
return false, err
377403
}
378404
if allowed {
379405
return true, nil
380406
}
381-
msg := fmt.Sprintf("User %s is not allowed to trigger CI %s on this repo.", p.event.Sender, viamsg)
407+
msg := fmt.Sprintf("User %s is not allowed to trigger CI %s in this repo.", p.event.Sender, viamsg)
382408
if p.event.AccountID != "" {
383-
msg = fmt.Sprintf("User: %s AccountID: %s is not allowed to trigger CI %s on this repo.", p.event.Sender, p.event.AccountID, viamsg)
409+
msg = fmt.Sprintf("User: %s AccountID: %s is not allowed to trigger CI %s in this repo.", p.event.Sender, p.event.AccountID, viamsg)
384410
}
385411
p.eventEmitter.EmitMessage(repo, zap.InfoLevel, "RepositoryPermissionDenied", msg)
386-
status := provider.StatusOpts{
387-
Status: queuedStatus,
388-
Title: "Pending approval, needs /ok-to-test",
389-
Conclusion: pendingConclusion,
390-
Text: msg,
391-
DetailsURL: p.event.URL,
392-
}
412+
status.Text = msg
393413
if err := p.vcx.CreateStatus(ctx, p.event, status); err != nil {
394414
return false, fmt.Errorf("failed to run create status, user is not allowed to run the CI:: %w", err)
395415
}

pkg/pipelineascode/pipelineascode_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,24 @@ func TestRun(t *testing.T) {
465465
finalStatusText: "PipelineRun has no taskruns",
466466
expectedNumberofCleanups: 10,
467467
},
468+
{
469+
name: "Do not allow unauthorized user to run CI on pushed commit",
470+
runevent: info.Event{
471+
SHA: "principale",
472+
Organization: "organizationes",
473+
Repository: "lagaffe",
474+
URL: "https://service/documentation",
475+
HeadBranch: "main",
476+
BaseBranch: "main",
477+
Sender: "fantasio",
478+
EventType: "test-all-comment",
479+
TriggerTarget: "push",
480+
},
481+
tektondir: "testdata/push_branch",
482+
finalStatus: "failure",
483+
finalStatusText: "User fantasio is not allowed to trigger CI by GitOps comment on push commit in this repo.",
484+
skipReplyingOrgPublicMembers: true,
485+
},
468486
}
469487
for _, tt := range tests {
470488
t.Run(tt.name, func(t *testing.T) {

pkg/provider/github/status.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const taskStatusTemplate = `
3535
{{- end }}
3636
</table>`
3737

38-
const pendingApproval = "Pending approval, needs /ok-to-test"
38+
const pendingApproval = "Pending approval, waiting for an /ok-to-test"
3939

4040
func (v *Provider) getExistingCheckRunID(ctx context.Context, runevent *info.Event, status provider.StatusOpts) (*int64, error) {
4141
opt := github.ListOptions{PerPage: v.PaginedNumber}

pkg/provider/github/status_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ func TestGithubProviderCreateStatus(t *testing.T) {
414414
"status": "queued",
415415
"conclusion": "pending",
416416
"output": {
417-
"title": "Pending approval, needs /ok-to-test",
417+
"title": "Pending approval, waiting for an /ok-to-test",
418418
"summary": "My CI is waiting for approval"
419419
}
420420
}

test/gitea_access_control_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ func TestGiteaPolicyOkToTestRetest(t *testing.T) {
142142
topts.GiteaCNX = normalUserCnx
143143
tgitea.PostCommentOnPullRequest(t, topts, okToTestComment)
144144
topts.CheckForStatus = "Skipped"
145-
topts.Regexp = regexp.MustCompile(fmt.Sprintf(".*User %s is not allowed to trigger CI via pull_request on this repo.", normalUser.UserName))
145+
topts.Regexp = regexp.MustCompile(fmt.Sprintf(".*User %s is not allowed to trigger CI via pull_request in this repo.", normalUser.UserName))
146146
tgitea.WaitForPullRequestCommentMatch(t, topts)
147147

148148
topts.ParamsRun.Clients.Log.Infof("Sending a /retest comment as a user not belonging to an allowed team in Repo CR policy but part of the organization")

test/pkg/gitea/test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ func WaitForStatus(t *testing.T, topts *TestOpts, ref, forcontext string, onlyla
404404
}
405405
for _, cstatus := range statuses {
406406
if topts.CheckForStatus == "Skipped" {
407-
if strings.HasSuffix(cstatus.Description, "Pending approval, needs /ok-to-test") {
407+
if strings.HasSuffix(cstatus.Description, "Pending approval, waiting for an /ok-to-test") {
408408
numstatus++
409409
break
410410
}

0 commit comments

Comments
 (0)