Skip to content

feat: add preset filter for audit logins #6001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Next Next commit
added migration for api key resource
  • Loading branch information
Kira-Pilot committed Jan 30, 2023
commit 0a0adfd2acb96fef12d65cc87e1a93d1eb54d7ab
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 @@ -484,6 +490,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
8 changes: 8 additions & 0 deletions coderd/audit/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,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 +88,9 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
return typed.UserID
case database.AuditableGroup:
return typed.Group.ID
case database.APIKey:
// this doesn't seem right
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
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.

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
18 changes: 16 additions & 2 deletions coderd/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -995,7 +995,19 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques
// @Success 201 {object} codersdk.LoginWithPasswordResponse
// @Router /users/login [post]
func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var (
ctx = r.Context()
auditor = api.Auditor.Load()
aReq, commitAudit = audit.InitRequest[database.APIKey](rw, &audit.RequestParams{
Audit: *auditor,
Log: api.Logger,
Request: r,
Action: database.AuditActionLogin,
})
)

defer commitAudit()

var loginWithPassword codersdk.LoginWithPasswordRequest
if !httpapi.Read(ctx, rw, r, &loginWithPassword) {
return
Expand Down Expand Up @@ -1043,7 +1055,7 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
return
}

cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
cookie, key, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: user.ID,
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
Expand All @@ -1056,6 +1068,8 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
return
}

aReq.New = *key

http.SetCookie(rw, cookie)

httpapi.Write(ctx, rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{
Expand Down
2 changes: 1 addition & 1 deletion coderd/workspaceapps.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ func (api *API) workspaceApplicationAuth(rw http.ResponseWriter, r *http.Request
if lifetime > int64((time.Hour * 24 * 7).Seconds()) {
lifetime = int64((time.Hour * 24 * 7).Seconds())
}
cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
cookie, _, err := api.createAPIKey(ctx, createAPIKeyParams{
UserID: apiKey.UserID,
LoginType: database.LoginTypePassword,
ExpiresAt: exp,
Expand Down
6 changes: 6 additions & 0 deletions codersdk/audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const (
AuditActionDelete AuditAction = "delete"
AuditActionStart AuditAction = "start"
AuditActionStop AuditAction = "stop"
AuditActionLogin AuditAction = "login"
AuditActionLogout AuditAction = "logout"
)

func (a AuditAction) Friendly() string {
Expand All @@ -71,6 +73,10 @@ func (a AuditAction) Friendly() string {
return "started"
case AuditActionStop:
return "stopped"
case AuditActionLogin:
return "logged in"
case AuditActionLogout:
return "logged out"
default:
return "unknown"
}
Expand Down
1 change: 1 addition & 0 deletions docs/admin/audit-logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ We track the following resources:

| <b>Resource<b> | |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| APIKey<br><i>write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>expires_at</td><td>false</td></tr><tr><td>hashed_secret</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>ip_address</td><td>false</td></tr><tr><td>last_user</td><td>false</td></tr><tr><td>lifetime_seconds</td><td>false</td></tr><tr><td>login_type</td><td>false</td></tr><tr><td>scope</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>false</td></tr></tbody></table> |
| Group<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>members</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>quota_allowance</td><td>true</td></tr></tbody></table> |
| GitSSHKey<br><i>create</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>private_key</td><td>true</td></tr><tr><td>public_key</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>is_private</td><td>true</td></tr><tr><td>min_autostart_interval</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
Expand Down
2 changes: 2 additions & 0 deletions docs/api/schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,8 @@
| `delete` |
| `start` |
| `stop` |
| `login` |
| `logout` |

## codersdk.AuditDiff

Expand Down
16 changes: 15 additions & 1 deletion enterprise/audit/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var AuditActionMap = map[string][]codersdk.AuditAction{
"Workspace": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete},
"WorkspaceBuild": {codersdk.AuditActionStart, codersdk.AuditActionStop},
"Group": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete},
"APIKey": {codersdk.AuditActionWrite},
}

type Action string
Expand Down Expand Up @@ -109,7 +110,6 @@ var AuditableResources = auditMap(map[any]map[string]Action{
"ttl": ActionTrack,
"last_used_at": ActionIgnore,
},
// We don't show any diff for the WorkspaceBuild resource
&database.WorkspaceBuild{}: {
"id": ActionIgnore,
"created_at": ActionIgnore,
Expand All @@ -133,6 +133,20 @@ var AuditableResources = auditMap(map[any]map[string]Action{
"quota_allowance": ActionTrack,
"members": ActionTrack,
},
// We don't show any diff for the APIKey resource
&database.APIKey{}: {
"id": ActionIgnore,
"hashed_secret": ActionIgnore,
"user_id": ActionIgnore,
"last_user": ActionIgnore,
"expires_at": ActionIgnore,
"created_at": ActionIgnore,
"updated_at": ActionIgnore,
"login_type": ActionIgnore,
"lifetime_seconds": ActionIgnore,
"ip_address": ActionIgnore,
"scope": ActionIgnore,
},
})

// auditMap converts a map of struct pointers to a map of struct names as
Expand Down
11 changes: 10 additions & 1 deletion site/src/api/typesGenerated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1030,10 +1030,19 @@ export type APIKeyScope = "all" | "application_connect"
export const APIKeyScopes: APIKeyScope[] = ["all", "application_connect"]

// From codersdk/audit.go
export type AuditAction = "create" | "delete" | "start" | "stop" | "write"
export type AuditAction =
| "create"
| "delete"
| "login"
| "logout"
| "start"
| "stop"
| "write"
export const AuditActions: AuditAction[] = [
"create",
"delete",
"login",
"logout",
"start",
"stop",
"write",
Expand Down