Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 7 additions & 7 deletions coderd/apikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
return
}

cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
cookie, _, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: user.ID,
LoginType: database.LoginTypeToken,
ExpiresAt: database.Now().Add(lifeTime),
Expand Down Expand Up @@ -108,7 +108,7 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
}

lifeTime := time.Hour * 24 * 7
cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
cookie, _, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: user.ID,
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
Expand Down Expand Up @@ -281,10 +281,10 @@ func (api *API) validateAPIKeyLifetime(lifetime time.Duration) error {
return nil
}

func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*http.Cookie, error) {
func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*http.Cookie, *database.APIKey, error) {
keyID, keySecret, err := GenerateAPIKeyIDSecret()
if err != nil {
return nil, xerrors.Errorf("generate API key: %w", err)
return nil, nil, xerrors.Errorf("generate API key: %w", err)
}
hashed := sha256.Sum256([]byte(keySecret))

Expand All @@ -310,7 +310,7 @@ func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*h
switch scope {
case database.APIKeyScopeAll, database.APIKeyScopeApplicationConnect:
default:
return nil, xerrors.Errorf("invalid API key scope: %q", scope)
return nil, nil, xerrors.Errorf("invalid API key scope: %q", scope)
}

key, err := api.Database.InsertAPIKey(ctx, database.InsertAPIKeyParams{
Expand All @@ -333,7 +333,7 @@ func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*h
Scope: scope,
})
if err != nil {
return nil, xerrors.Errorf("insert API key: %w", err)
return nil, nil, xerrors.Errorf("insert API key: %w", err)
}

api.Telemetry.Report(&telemetry.Snapshot{
Expand All @@ -349,5 +349,5 @@ func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*h
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
Secure: api.SecureAuthCookie,
}, nil
}, &key, nil
}
10 changes: 10 additions & 0 deletions coderd/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ func auditLogDescription(alog database.GetAuditLogsOffsetRow, additionalFields a
codersdk.AuditAction(alog.Action).Friendly(),
)

// API Key resources do not have targets and follow the below format:
// "User {logged in | logged out}"
if alog.ResourceType == database.ResourceTypeApiKey {
return str
}

// Strings for starting/stopping workspace builds follow the below format:
// "{user | 'Coder automatically'} started build #{build_number} for workspace {target}"
// where target is a workspace (name) instead of a workspace build
Expand Down Expand Up @@ -488,6 +494,10 @@ func actionFromString(actionString string) string {
return actionString
case codersdk.AuditActionStop:
return actionString
case codersdk.AuditActionLogin:
return actionString
case codersdk.AuditActionLogout:
return actionString
default:
}
return ""
Expand Down
22 changes: 19 additions & 3 deletions coderd/audit/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ type RequestParams struct {
type Request[T Auditable] struct {
params *RequestParams

Old T
New T
Old T
New T
UserId uuid.UUID
}

type BuildAuditParams[T Auditable] struct {
Expand Down Expand Up @@ -64,6 +65,9 @@ func ResourceTarget[T Auditable](tgt T) string {
return typed.PublicKey
case database.AuditableGroup:
return typed.Group.Name
case database.APIKey:
// this isn't used
return ""
default:
panic(fmt.Sprintf("unknown resource %T", tgt))
}
Expand All @@ -85,6 +89,8 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
return typed.UserID
case database.AuditableGroup:
return typed.Group.ID
case database.APIKey:
return typed.UserID
default:
panic(fmt.Sprintf("unknown resource %T", tgt))
}
Expand All @@ -106,6 +112,8 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
return database.ResourceTypeGitSshKey
case database.AuditableGroup:
return database.ResourceTypeGroup
case database.APIKey:
return database.ResourceTypeApiKey
default:
panic(fmt.Sprintf("unknown resource %T", tgt))
}
Expand Down Expand Up @@ -150,11 +158,19 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
p.AdditionalFields = json.RawMessage("{}")
}

var userID uuid.UUID
key, ok := httpmw.APIKeyOptional(p.Request)
if ok {
userID = key.UserID
} else {
userID = req.UserId
}

ip := parseIP(p.Request.RemoteAddr)
auditLog := database.AuditLog{
ID: uuid.New(),
Time: database.Now(),
UserID: httpmw.APIKey(p.Request).UserID,
UserID: userID,
Ip: ip,
UserAgent: sql.NullString{String: p.Request.UserAgent(), Valid: true},
ResourceType: either(req.Old, req.New, ResourceType[T]),
Expand Down
4 changes: 3 additions & 1 deletion coderd/database/dump.sql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- It's not possible to drop enum values from enum types, so the UP has "IF NOT
-- EXISTS".
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ALTER TYPE audit_action
ADD VALUE IF NOT EXISTS 'login';

ALTER TYPE audit_action
ADD VALUE IF NOT EXISTS 'logout';

ALTER TYPE resource_type
ADD VALUE IF NOT EXISTS 'api_key';

8 changes: 7 additions & 1 deletion coderd/database/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions coderd/gitsshkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ func TestGitSSHKey(t *testing.T) {
require.NotEmpty(t, key2.PublicKey)
require.NotEqual(t, key2.PublicKey, key1.PublicKey)

require.Len(t, auditor.AuditLogs, 1)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[0].Action)
require.Len(t, auditor.AuditLogs, 2)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[1].Action)
})
}

Expand Down
17 changes: 9 additions & 8 deletions coderd/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@ func TestPostTemplateByOrganization(t *testing.T) {
assert.Equal(t, expected.Name, got.Name)
assert.Equal(t, expected.Description, got.Description)

require.Len(t, auditor.AuditLogs, 3)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[0].Action)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[1].Action)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[2].Action)
require.Len(t, auditor.AuditLogs, 4)
assert.Equal(t, database.AuditActionLogin, auditor.AuditLogs[0].Action)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[1].Action)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[2].Action)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[3].Action)
})

t.Run("AlreadyExists", func(t *testing.T) {
Expand Down Expand Up @@ -320,8 +321,8 @@ func TestPatchTemplateMeta(t *testing.T) {
assert.Equal(t, req.DefaultTTLMillis, updated.DefaultTTLMillis)
assert.False(t, req.AllowUserCancelWorkspaceJobs)

require.Len(t, auditor.AuditLogs, 4)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[3].Action)
require.Len(t, auditor.AuditLogs, 5)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[4].Action)
})

t.Run("NoMaxTTL", func(t *testing.T) {
Expand Down Expand Up @@ -483,8 +484,8 @@ func TestDeleteTemplate(t *testing.T) {
err := client.DeleteTemplate(ctx, template.ID)
require.NoError(t, err)

require.Len(t, auditor.AuditLogs, 4)
assert.Equal(t, database.AuditActionDelete, auditor.AuditLogs[3].Action)
require.Len(t, auditor.AuditLogs, 5)
assert.Equal(t, database.AuditActionDelete, auditor.AuditLogs[4].Action)
})

t.Run("Workspaces", func(t *testing.T) {
Expand Down
8 changes: 4 additions & 4 deletions coderd/templateversions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ func TestPostTemplateVersionsByOrganization(t *testing.T) {
require.Equal(t, "bananas", version.Name)
require.Equal(t, provisionerdserver.ScopeOrganization, version.Job.Tags[provisionerdserver.TagScope])

require.Len(t, auditor.AuditLogs, 1)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[0].Action)
require.Len(t, auditor.AuditLogs, 2)
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[1].Action)
})
t.Run("Example", func(t *testing.T) {
t.Parallel()
Expand Down Expand Up @@ -645,8 +645,8 @@ func TestPatchActiveTemplateVersion(t *testing.T) {
})
require.NoError(t, err)

require.Len(t, auditor.AuditLogs, 4)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[3].Action)
require.Len(t, auditor.AuditLogs, 5)
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[4].Action)
})
}

Expand Down
2 changes: 1 addition & 1 deletion coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
return nil, xerrors.Errorf("in tx: %w", err)
}

cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
cookie, _, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: user.ID,
LoginType: params.LoginType,
RemoteAddr: r.RemoteAddr,
Expand Down
Loading