From 8386d4491ab8106e8a411cd94ef30ac001953085 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 2 Sep 2025 12:32:32 +0100 Subject: [PATCH 1/2] fix(coderd): add audit log on creating a new session key --- coderd/apikey.go | 18 +++++++++++++++--- coderd/apikey_test.go | 11 +++++++++-- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/coderd/apikey.go b/coderd/apikey.go index d1db511c13ae1..9063f0dac7806 100644 --- a/coderd/apikey.go +++ b/coderd/apikey.go @@ -131,8 +131,19 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) { // @Success 201 {object} codersdk.GenerateAPIKeyResponse // @Router /users/{user}/keys [post] func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) { - ctx := r.Context() - user := httpmw.UserParam(r) + var ( + ctx = r.Context() + user = httpmw.UserParam(r) + auditor = api.Auditor.Load() + aReq, commitAudit = audit.InitRequest[database.APIKey](rw, &audit.RequestParams{ + Audit: *auditor, + Log: api.Logger, + Request: r, + Action: database.AuditActionCreate, + }) + ) + aReq.Old = database.APIKey{} + defer commitAudit() // TODO(Cian): System users technically just have the 'member' role // and we don't want to disallow all members from creating API keys. @@ -142,7 +153,7 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) { return } - cookie, _, err := api.createAPIKey(ctx, apikey.CreateParams{ + cookie, key, err := api.createAPIKey(ctx, apikey.CreateParams{ UserID: user.ID, DefaultLifetime: api.DeploymentValues.Sessions.DefaultTokenDuration.Value(), LoginType: database.LoginTypePassword, @@ -156,6 +167,7 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) { return } + aReq.New = *key // We intentionally do not set the cookie on the response here. // Setting the cookie will couple the browser session to the API // key we return here, meaning logging out of the website would diff --git a/coderd/apikey_test.go b/coderd/apikey_test.go index 1509aa2e2f402..858c57c329cf1 100644 --- a/coderd/apikey_test.go +++ b/coderd/apikey_test.go @@ -305,12 +305,19 @@ func TestAPIKey_OK(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) - _ = coderdtest.CreateFirstUser(t, client) + auditor := audit.NewMock() + client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor}) + owner := coderdtest.CreateFirstUser(t, client) + auditor.ResetLogs() res, err := client.CreateAPIKey(ctx, codersdk.Me) require.NoError(t, err) require.Greater(t, len(res.Key), 2) + require.True(t, auditor.Contains(t, database.AuditLog{ + UserID: owner.UserID, + Action: database.AuditActionCreate, + ResourceType: database.ResourceTypeApiKey, + })) } func TestAPIKey_Deleted(t *testing.T) { From 6a92b2abe60a9d4d2c26ce585bb6907dc4a0bca0 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Tue, 2 Sep 2025 14:10:22 +0100 Subject: [PATCH 2/2] test harder --- coderd/apikey_test.go | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/coderd/apikey_test.go b/coderd/apikey_test.go index 858c57c329cf1..73655754a8fb3 100644 --- a/coderd/apikey_test.go +++ b/coderd/apikey_test.go @@ -2,6 +2,7 @@ package coderd_test import ( "context" + "encoding/json" "net/http" "strings" "testing" @@ -303,21 +304,32 @@ func TestSessionExpiry(t *testing.T) { func TestAPIKey_OK(t *testing.T) { t.Parallel() + + // Given: a deployment with auditing enabled ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() auditor := audit.NewMock() client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor}) owner := coderdtest.CreateFirstUser(t, client) - auditor.ResetLogs() + + // When: an API key is created res, err := client.CreateAPIKey(ctx, codersdk.Me) require.NoError(t, err) require.Greater(t, len(res.Key), 2) - require.True(t, auditor.Contains(t, database.AuditLog{ - UserID: owner.UserID, - Action: database.AuditActionCreate, - ResourceType: database.ResourceTypeApiKey, - })) + + // Then: an audit log is generated + als := auditor.AuditLogs() + require.Len(t, als, 1) + al := als[0] + assert.Equal(t, owner.UserID, al.UserID) + assert.Equal(t, database.AuditActionCreate, al.Action) + assert.Equal(t, database.ResourceTypeApiKey, al.ResourceType) + + // Then: the diff MUST NOT contain the generated key. + raw, err := json.Marshal(al) + require.NoError(t, err) + require.NotContains(t, res.Key, string(raw)) } func TestAPIKey_Deleted(t *testing.T) {