Skip to content

Commit f07a394

Browse files
mbpavanpipelines-as-code[bot]
authored andcommitted
feat(security): add audit logging to clients in pipelines-as-code
Log access using access tokens which allows users to review those logs.
1 parent b772b84 commit f07a394

File tree

14 files changed

+136
-19
lines changed

14 files changed

+136
-19
lines changed

pkg/provider/bitbucketcloud/bitbucket.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ func (v *Provider) SetClient(_ context.Context, run *params.Run, event *info.Eve
203203
return fmt.Errorf("no git_provider.user has been in repo crd")
204204
}
205205
v.bbClient = bitbucket.NewBasicAuth(event.Provider.User, event.Provider.Token)
206+
207+
// Added log for security audit purposes to log client access when a token is used
208+
run.Clients.Log.Infof("bitbucket-cloud: initialized client with provided token for user=%s", event.Provider.User)
209+
206210
v.Token = &event.Provider.Token
207211
v.Username = &event.Provider.User
208212
v.run = run

pkg/provider/bitbucketcloud/bitbucket_test.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package bitbucketcloud
22

33
import (
4+
"fmt"
45
"strings"
56
"testing"
67

78
"github.com/ktrysmt/go-bitbucket"
89
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
10+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/clients"
911
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
1012
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/settings"
1113
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
@@ -145,14 +147,29 @@ func TestSetClient(t *testing.T) {
145147
for _, tt := range tests {
146148
t.Run(tt.name, func(t *testing.T) {
147149
ctx, _ := rtesting.SetupFakeContext(t)
150+
core, observer := zapobserver.New(zap.InfoLevel)
151+
testLog := zap.New(core).Sugar()
152+
153+
fakeRun := &params.Run{
154+
Clients: clients.Clients{
155+
Log: testLog,
156+
},
157+
}
158+
148159
v := Provider{}
149-
err := v.SetClient(ctx, nil, tt.event, nil, nil)
160+
err := v.SetClient(ctx, fakeRun, tt.event, nil, nil)
161+
150162
if tt.wantErrSubstr != "" {
151163
assert.ErrorContains(t, err, tt.wantErrSubstr)
152164
return
153165
}
154166
assert.Equal(t, tt.event.Provider.Token, *v.Token)
155167
assert.Equal(t, tt.event.Provider.User, *v.Username)
168+
169+
logs := observer.TakeAll()
170+
assert.Assert(t, len(logs) > 0, "expected a log entry, got none")
171+
expected := fmt.Sprintf("bitbucket-cloud: initialized client with provided token for user=%s", tt.event.Provider.User)
172+
assert.Equal(t, expected, logs[0].Message)
156173
})
157174
}
158175
}

pkg/provider/bitbucketdatacenter/bitbucketdatacenter.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ func (v *Provider) SetClient(ctx context.Context, run *params.Run, event *info.E
307307
},
308308
}
309309
v.client = client
310+
311+
// Added for security audit purposes to log client access when a token is used
312+
run.Clients.Log.Infof("bitbucket-datacenter: initialized client with provided token for user=%s providerURL=%s", event.Provider.User, event.Provider.URL)
310313
}
311314
v.run = run
312315
v.repo = repo

pkg/provider/bitbucketdatacenter/bitbucketdatacenter_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"testing"
1616

1717
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
18+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/clients"
1819
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
1920
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/settings"
2021
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
@@ -362,14 +363,21 @@ func TestSetClient(t *testing.T) {
362363
}
363364
for _, tt := range tests {
364365
t.Run(tt.name, func(t *testing.T) {
366+
observer, _ := zapobserver.New(zap.InfoLevel)
367+
testLog := zap.New(observer).Sugar()
368+
fakeRun := &params.Run{
369+
Clients: clients.Clients{
370+
Log: testLog,
371+
},
372+
}
365373
ctx, _ := rtesting.SetupFakeContext(t)
366374
client, mux, tearDown, tURL := bbtest.SetupBBDataCenterClient()
367375
defer tearDown()
368376
if tt.muxUser != nil {
369377
mux.HandleFunc("/users/foo", tt.muxUser)
370378
}
371379
v := &Provider{client: client, baseURL: tURL}
372-
err := v.SetClient(ctx, nil, tt.opts, nil, nil)
380+
err := v.SetClient(ctx, fakeRun, tt.opts, nil, nil)
373381
if tt.wantErrSubstr != "" {
374382
assert.ErrorContains(t, err, tt.wantErrSubstr)
375383
return

pkg/provider/gitea/gitea.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ func (v *Provider) SetClient(_ context.Context, run *params.Run, runevent *info.
159159
if err != nil {
160160
return err
161161
}
162+
163+
// Added log for security audit purposes to log client access when a token is used
164+
run.Clients.Log.Infof("gitea: initialized API client with provided credentials user=%s providerURL=%s", runevent.Provider.User, apiURL)
165+
162166
v.giteaInstanceURL = runevent.Provider.URL
163167
v.eventEmitter = emitter
164168
v.repo = repo

pkg/provider/github/github.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,13 @@ func (v *Provider) SetClient(ctx context.Context, run *params.Run, event *info.E
301301
return fmt.Errorf("no github client has been initialized")
302302
}
303303

304+
// Added log for security audit purposes to log client access when a token is used
305+
integration := "github-webhook"
306+
if event.InstallationID != 0 {
307+
integration = "github-app"
308+
}
309+
run.Clients.Log.Infof(integration+": initialized OAuth2 client for providerName=%s providerURL=%s", v.providerName, event.Provider.URL)
310+
304311
v.APIURL = apiURL
305312

306313
if event.Provider.WebhookSecretFromRepo {

pkg/provider/github/github_test.go

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -657,10 +657,11 @@ func TestGithubGetCommitInfo(t *testing.T) {
657657

658658
func TestGithubSetClient(t *testing.T) {
659659
tests := []struct {
660-
name string
661-
event *info.Event
662-
expectedURL string
663-
isGHE bool
660+
name string
661+
event *info.Event
662+
expectedURL string
663+
isGHE bool
664+
installationID int64
664665
}{
665666
{
666667
name: "api url set",
@@ -669,20 +670,30 @@ func TestGithubSetClient(t *testing.T) {
669670
URL: "foo.com",
670671
},
671672
},
672-
expectedURL: "https://foo.com",
673-
isGHE: true,
673+
expectedURL: "https://foo.com",
674+
isGHE: true,
675+
installationID: 0,
674676
},
675677
{
676-
name: "default to public github",
677-
expectedURL: fmt.Sprintf("%s/", keys.PublicGithubAPIURL),
678-
event: info.NewEvent(),
678+
name: "default to public github",
679+
expectedURL: fmt.Sprintf("%s/", keys.PublicGithubAPIURL),
680+
event: info.NewEvent(),
681+
installationID: 12345,
679682
},
680683
}
681684
for _, tt := range tests {
682685
t.Run(tt.name, func(t *testing.T) {
686+
tt.event.InstallationID = tt.installationID
683687
ctx, _ := rtesting.SetupFakeContext(t)
688+
core, observer := zapobserver.New(zap.InfoLevel)
689+
testLog := zap.New(core).Sugar()
690+
fakeRun := &params.Run{
691+
Clients: clients.Clients{
692+
Log: testLog,
693+
},
694+
}
684695
v := Provider{}
685-
err := v.SetClient(ctx, nil, tt.event, nil, nil)
696+
err := v.SetClient(ctx, fakeRun, tt.event, nil, nil)
686697
assert.NilError(t, err)
687698
assert.Equal(t, tt.expectedURL, *v.APIURL)
688699
assert.Equal(t, "https", v.Client().BaseURL.Scheme)
@@ -691,6 +702,33 @@ func TestGithubSetClient(t *testing.T) {
691702
} else {
692703
assert.Equal(t, "/", v.Client().BaseURL.Path)
693704
}
705+
706+
logs := observer.TakeAll()
707+
assert.Assert(t, len(logs) == 1, "expected exactly one log entry, got %d", len(logs))
708+
709+
prefix := "github-webhook"
710+
if tt.installationID != 0 {
711+
prefix = "github-app"
712+
}
713+
wantStart := fmt.Sprintf("%s: initialized OAuth2 client", prefix)
714+
got := logs[0].Message
715+
assert.Assert(t, strings.HasPrefix(got, wantStart), "log entry should start with %q, got %q", wantStart, got)
716+
717+
// Determine expected providerName based on whether it's GHE or public GitHub.
718+
expectedProviderName := "github"
719+
if tt.isGHE {
720+
expectedProviderName = "github-enterprise"
721+
}
722+
723+
// Build the full expected log message.
724+
fullExpected := fmt.Sprintf(
725+
"%s: initialized OAuth2 client for providerName=%s providerURL=%s",
726+
prefix,
727+
expectedProviderName,
728+
tt.event.Provider.URL,
729+
)
730+
731+
assert.Equal(t, fullExpected, logs[0].Message)
694732
})
695733
}
696734
}

pkg/provider/gitlab/gitlab.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ func (v *Provider) SetClient(_ context.Context, run *params.Run, runevent *info.
201201
}
202202
v.Token = &runevent.Provider.Token
203203

204+
run.Clients.Log.Infof("gitlab: initialized for client with token for apiURL=%s, org=%s, repo=%s", apiURL, runevent.Organization, runevent.Repository)
204205
// In a scenario where the source repository is forked and a merge request (MR) is created on the upstream
205206
// repository, runevent.SourceProjectID will not be 0 when SetClient is called from the pac-watcher code.
206207
// This is because, in the controller, SourceProjectID is set in the annotation of the pull request,

pkg/provider/gitlab/gitlab_test.go

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ func TestCreateStatus(t *testing.T) {
210210
run := &params.Run{
211211
Clients: clients.Clients{
212212
Kube: stdata.Kube,
213+
Log: logger,
213214
},
214215
}
215216
v := &Provider{
@@ -261,23 +262,54 @@ func TestGetConfig(t *testing.T) {
261262

262263
func TestSetClient(t *testing.T) {
263264
ctx, _ := rtesting.SetupFakeContext(t)
265+
core, observer := zapobserver.New(zap.InfoLevel)
266+
fakelogger := zap.New(core).Sugar()
267+
268+
run := &params.Run{
269+
Clients: clients.Clients{
270+
Log: fakelogger,
271+
},
272+
}
273+
264274
v := &Provider{}
265-
assert.Assert(t, v.SetClient(ctx, nil, info.NewEvent(), nil, nil) != nil)
275+
assert.Assert(t, v.SetClient(ctx, run, info.NewEvent(), nil, nil) != nil)
266276

267277
client, _, tearDown := thelp.Setup(t)
268278
defer tearDown()
279+
269280
vv := &Provider{gitlabClient: client}
270-
err := vv.SetClient(ctx, nil, &info.Event{
281+
err := vv.SetClient(ctx, run, &info.Event{
271282
Provider: &info.Provider{
272283
Token: "hello",
273284
},
285+
Organization: "my-org",
286+
Repository: "my-repo",
287+
SourceProjectID: 1234,
288+
TargetProjectID: 1234,
274289
}, nil, nil)
290+
275291
assert.NilError(t, err)
276292
assert.Assert(t, *vv.Token != "")
293+
294+
logs := observer.TakeAll()
295+
assert.Assert(t, len(logs) > 0, "expected a log entry, got none")
296+
297+
expected := fmt.Sprintf(
298+
"gitlab: initialized for client with token for apiURL=%s, org=%s, repo=%s",
299+
vv.apiURL, "my-org", "my-repo")
300+
301+
assert.Equal(t, expected, logs[0].Message)
277302
}
278303

279304
func TestSetClientDetectAPIURL(t *testing.T) {
280305
ctx, _ := rtesting.SetupFakeContext(t)
306+
observer, _ := zapobserver.New(zap.InfoLevel)
307+
fakelogger := zap.New(observer).Sugar()
308+
run := &params.Run{
309+
Clients: clients.Clients{
310+
Log: fakelogger,
311+
},
312+
}
281313
mockClient, _, tearDown := thelp.Setup(t)
282314
defer tearDown()
283315

@@ -394,7 +426,7 @@ func TestSetClientDetectAPIURL(t *testing.T) {
394426

395427
// Execute the function under test
396428
// Using placeholder nil values for arguments not directly related to URL detection
397-
err := v.SetClient(ctx, nil, event, nil, nil)
429+
err := v.SetClient(ctx, run, event, nil, nil)
398430

399431
// Assertions
400432
if tc.expectedError != "" {

pkg/reconciler/reconciler.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ func (r *Reconciler) reportFinalStatus(ctx context.Context, logger *zap.SugaredL
173173
}
174174
}
175175

176+
if r.run.Clients.Log == nil {
177+
r.run.Clients.Log = logger
178+
}
176179
err = provider.SetClient(ctx, r.run, event, repo, r.eventEmitter)
177180
if err != nil {
178181
return repo, fmt.Errorf("cannot set client: %w", err)

0 commit comments

Comments
 (0)