Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: add sql filter to fetching audit logs#14070

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Emyrk merged 8 commits intomainfromstevenmasley/audit_log_sql_filter
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes fromall 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
86 changes: 43 additions & 43 deletionscoderd/audit.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -182,17 +182,17 @@ func (api *API) convertAuditLogs(ctx context.Context, dblogs []database.GetAudit
}

func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog {
ip, _ := netip.AddrFromSlice(dblog.Ip.IPNet.IP)
ip, _ := netip.AddrFromSlice(dblog.AuditLog.Ip.IPNet.IP)

diff := codersdk.AuditDiff{}
_ = json.Unmarshal(dblog.Diff, &diff)
_ = json.Unmarshal(dblog.AuditLog.Diff, &diff)

var user *codersdk.User
if dblog.UserUsername.Valid {
// Leaving the organization IDs blank for now; not sure they are useful for
// the audit query anyway?
sdkUser := db2sdk.User(database.User{
ID: dblog.UserID,
ID: dblog.AuditLog.UserID,
Email: dblog.UserEmail.String,
Username: dblog.UserUsername.String,
CreatedAt: dblog.UserCreatedAt.Time,
Expand All@@ -211,7 +211,7 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
}

var (
additionalFieldsBytes = []byte(dblog.AdditionalFields)
additionalFieldsBytes = []byte(dblog.AuditLog.AdditionalFields)
additionalFields audit.AdditionalFields
err = json.Unmarshal(additionalFieldsBytes, &additionalFields)
)
Expand All@@ -224,7 +224,7 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
WorkspaceOwner: "unknown",
}

dblog.AdditionalFields, err = json.Marshal(resourceInfo)
dblog.AuditLog.AdditionalFields, err = json.Marshal(resourceInfo)
api.Logger.Error(ctx, "marshal additional fields", slog.Error(err))
}

Expand All@@ -239,30 +239,30 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
}

alog := codersdk.AuditLog{
ID: dblog.ID,
RequestID: dblog.RequestID,
Time: dblog.Time,
ID: dblog.AuditLog.ID,
RequestID: dblog.AuditLog.RequestID,
Time: dblog.AuditLog.Time,
// OrganizationID is deprecated.
OrganizationID: dblog.OrganizationID,
OrganizationID: dblog.AuditLog.OrganizationID,
IP: ip,
UserAgent: dblog.UserAgent.String,
ResourceType: codersdk.ResourceType(dblog.ResourceType),
ResourceID: dblog.ResourceID,
ResourceTarget: dblog.ResourceTarget,
ResourceIcon: dblog.ResourceIcon,
Action: codersdk.AuditAction(dblog.Action),
UserAgent: dblog.AuditLog.UserAgent.String,
ResourceType: codersdk.ResourceType(dblog.AuditLog.ResourceType),
ResourceID: dblog.AuditLog.ResourceID,
ResourceTarget: dblog.AuditLog.ResourceTarget,
ResourceIcon: dblog.AuditLog.ResourceIcon,
Action: codersdk.AuditAction(dblog.AuditLog.Action),
Diff: diff,
StatusCode: dblog.StatusCode,
AdditionalFields: dblog.AdditionalFields,
StatusCode: dblog.AuditLog.StatusCode,
AdditionalFields: dblog.AuditLog.AdditionalFields,
User: user,
Description: auditLogDescription(dblog),
ResourceLink: resourceLink,
IsDeleted: isDeleted,
}

if dblog.OrganizationID != uuid.Nil {
if dblog.AuditLog.OrganizationID != uuid.Nil {
alog.Organization = &codersdk.MinimalOrganization{
ID: dblog.OrganizationID,
ID: dblog.AuditLog.OrganizationID,
Name: dblog.OrganizationName,
DisplayName: dblog.OrganizationDisplayName,
Icon: dblog.OrganizationIcon,
Expand All@@ -276,32 +276,32 @@ func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
b := strings.Builder{}
// NOTE: WriteString always returns a nil error, so we never check it
_, _ = b.WriteString("{user} ")
if alog.StatusCode >= 400 {
if alog.AuditLog.StatusCode >= 400 {
_, _ = b.WriteString("unsuccessfully attempted to ")
_, _ = b.WriteString(string(alog.Action))
_, _ = b.WriteString(string(alog.AuditLog.Action))
} else {
_, _ = b.WriteString(codersdk.AuditAction(alog.Action).Friendly())
_, _ = b.WriteString(codersdk.AuditAction(alog.AuditLog.Action).Friendly())
}

// API Key resources (used for authentication) do not have targets and follow the below format:
// "User {logged in | logged out | registered}"
if alog.ResourceType == database.ResourceTypeApiKey &&
(alog.Action == database.AuditActionLogin || alog.Action == database.AuditActionLogout || alog.Action == database.AuditActionRegister) {
if alog.AuditLog.ResourceType == database.ResourceTypeApiKey &&
(alog.AuditLog.Action == database.AuditActionLogin || alog.AuditLog.Action == database.AuditActionLogout || alog.AuditLog.Action == database.AuditActionRegister) {
return b.String()
}

// We don't display the name (target) for git ssh keys. It's fairly long and doesn't
// make too much sense to display.
if alog.ResourceType == database.ResourceTypeGitSshKey {
if alog.AuditLog.ResourceType == database.ResourceTypeGitSshKey {
_, _ = b.WriteString(" the ")
_, _ = b.WriteString(codersdk.ResourceType(alog.ResourceType).FriendlyString())
_, _ = b.WriteString(codersdk.ResourceType(alog.AuditLog.ResourceType).FriendlyString())
return b.String()
}

_, _ = b.WriteString(" ")
_, _ = b.WriteString(codersdk.ResourceType(alog.ResourceType).FriendlyString())
_, _ = b.WriteString(codersdk.ResourceType(alog.AuditLog.ResourceType).FriendlyString())

if alog.ResourceType == database.ResourceTypeConvertLogin {
if alog.AuditLog.ResourceType == database.ResourceTypeConvertLogin {
_, _ = b.WriteString(" to")
}

Expand All@@ -311,9 +311,9 @@ func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
}

func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.GetAuditLogsOffsetRow) bool {
switch alog.ResourceType {
switch alog.AuditLog.ResourceType {
case database.ResourceTypeTemplate:
template, err := api.Database.GetTemplateByID(ctx, alog.ResourceID)
template, err := api.Database.GetTemplateByID(ctx, alog.AuditLog.ResourceID)
if err != nil {
if xerrors.Is(err, sql.ErrNoRows) {
return true
Expand All@@ -322,7 +322,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
}
return template.Deleted
case database.ResourceTypeUser:
user, err := api.Database.GetUserByID(ctx, alog.ResourceID)
user, err := api.Database.GetUserByID(ctx, alog.AuditLog.ResourceID)
if err != nil {
if xerrors.Is(err, sql.ErrNoRows) {
return true
Expand All@@ -331,7 +331,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
}
return user.Deleted
case database.ResourceTypeWorkspace:
workspace, err := api.Database.GetWorkspaceByID(ctx, alog.ResourceID)
workspace, err := api.Database.GetWorkspaceByID(ctx, alog.AuditLog.ResourceID)
if err != nil {
if xerrors.Is(err, sql.ErrNoRows) {
return true
Expand All@@ -340,7 +340,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
}
return workspace.Deleted
case database.ResourceTypeWorkspaceBuild:
workspaceBuild, err := api.Database.GetWorkspaceBuildByID(ctx, alog.ResourceID)
workspaceBuild, err := api.Database.GetWorkspaceBuildByID(ctx, alog.AuditLog.ResourceID)
if err != nil {
if xerrors.Is(err, sql.ErrNoRows) {
return true
Expand All@@ -357,15 +357,15 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
}
return workspace.Deleted
case database.ResourceTypeOauth2ProviderApp:
_, err := api.Database.GetOAuth2ProviderAppByID(ctx, alog.ResourceID)
_, err := api.Database.GetOAuth2ProviderAppByID(ctx, alog.AuditLog.ResourceID)
if xerrors.Is(err, sql.ErrNoRows) {
return true
} else if err != nil {
api.Logger.Error(ctx, "unable to fetch oauth2 app", slog.Error(err))
}
return false
case database.ResourceTypeOauth2ProviderAppSecret:
_, err := api.Database.GetOAuth2ProviderAppSecretByID(ctx, alog.ResourceID)
_, err := api.Database.GetOAuth2ProviderAppSecretByID(ctx, alog.AuditLog.ResourceID)
if xerrors.Is(err, sql.ErrNoRows) {
return true
} else if err != nil {
Expand All@@ -378,17 +378,17 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
}

func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAuditLogsOffsetRow, additionalFields audit.AdditionalFields) string {
switch alog.ResourceType {
switch alog.AuditLog.ResourceType {
case database.ResourceTypeTemplate:
return fmt.Sprintf("/templates/%s",
alog.ResourceTarget)
alog.AuditLog.ResourceTarget)

case database.ResourceTypeUser:
return fmt.Sprintf("/users?filter=%s",
alog.ResourceTarget)
alog.AuditLog.ResourceTarget)

case database.ResourceTypeWorkspace:
workspace, getWorkspaceErr := api.Database.GetWorkspaceByID(ctx, alog.ResourceID)
workspace, getWorkspaceErr := api.Database.GetWorkspaceByID(ctx, alog.AuditLog.ResourceID)
if getWorkspaceErr != nil {
return ""
}
Expand All@@ -397,13 +397,13 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit
return ""
}
return fmt.Sprintf("/@%s/%s",
workspaceOwner.Username, alog.ResourceTarget)
workspaceOwner.Username, alog.AuditLog.ResourceTarget)

case database.ResourceTypeWorkspaceBuild:
if len(additionalFields.WorkspaceName) == 0 || len(additionalFields.BuildNumber) == 0 {
return ""
}
workspaceBuild, getWorkspaceBuildErr := api.Database.GetWorkspaceBuildByID(ctx, alog.ResourceID)
workspaceBuild, getWorkspaceBuildErr := api.Database.GetWorkspaceBuildByID(ctx, alog.AuditLog.ResourceID)
if getWorkspaceBuildErr != nil {
return ""
}
Expand All@@ -419,10 +419,10 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit
workspaceOwner.Username, additionalFields.WorkspaceName, additionalFields.BuildNumber)

case database.ResourceTypeOauth2ProviderApp:
return fmt.Sprintf("/deployment/oauth2-provider/apps/%s", alog.ResourceID)
return fmt.Sprintf("/deployment/oauth2-provider/apps/%s", alog.AuditLog.ResourceID)

case database.ResourceTypeOauth2ProviderAppSecret:
secret, err := api.Database.GetOAuth2ProviderAppSecretByID(ctx, alog.ResourceID)
secret, err := api.Database.GetOAuth2ProviderAppSecretByID(ctx, alog.AuditLog.ResourceID)
if err != nil {
return ""
}
Expand Down
40 changes: 25 additions & 15 deletionscoderd/audit_internal_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,45 +18,55 @@ func TestAuditLogDescription(t *testing.T) {
{
name: "mainline",
alog: database.GetAuditLogsOffsetRow{
Action: database.AuditActionCreate,
StatusCode: 200,
ResourceType: database.ResourceTypeWorkspace,
AuditLog: database.AuditLog{
Action: database.AuditActionCreate,
StatusCode: 200,
ResourceType: database.ResourceTypeWorkspace,
},
},
want: "{user} created workspace {target}",
},
{
name: "unsuccessful",
alog: database.GetAuditLogsOffsetRow{
Action: database.AuditActionCreate,
StatusCode: 400,
ResourceType: database.ResourceTypeWorkspace,
AuditLog: database.AuditLog{
Action: database.AuditActionCreate,
StatusCode: 400,
ResourceType: database.ResourceTypeWorkspace,
},
},
want: "{user} unsuccessfully attempted to create workspace {target}",
},
{
name: "login",
alog: database.GetAuditLogsOffsetRow{
Action: database.AuditActionLogin,
StatusCode: 200,
ResourceType: database.ResourceTypeApiKey,
AuditLog: database.AuditLog{
Action: database.AuditActionLogin,
StatusCode: 200,
ResourceType: database.ResourceTypeApiKey,
},
},
want: "{user} logged in",
},
{
name: "unsuccessful_login",
alog: database.GetAuditLogsOffsetRow{
Action: database.AuditActionLogin,
StatusCode: 401,
ResourceType: database.ResourceTypeApiKey,
AuditLog: database.AuditLog{
Action: database.AuditActionLogin,
StatusCode: 401,
ResourceType: database.ResourceTypeApiKey,
},
},
want: "{user} unsuccessfully attempted to login",
},
{
name: "gitsshkey",
alog: database.GetAuditLogsOffsetRow{
Action: database.AuditActionDelete,
StatusCode: 200,
ResourceType: database.ResourceTypeGitSshKey,
AuditLog: database.AuditLog{
Action: database.AuditActionDelete,
StatusCode: 200,
ResourceType: database.ResourceTypeGitSshKey,
},
},
want: "{user} deleted the git ssh key",
},
Expand Down
14 changes: 6 additions & 8 deletionscoderd/audit_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"testing"
"time"
Expand DownExpand Up@@ -163,19 +162,18 @@ func TestAuditLogs(t *testing.T) {
})
require.NoError(t, err)

// Fetching audit logs without an organization selector should fail
_, err = orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
// Fetching audit logs without an organization selector should only
// return organization audit logs the org admin is an admin of.
alogs, err := orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
Pagination: codersdk.Pagination{
Limit: 5,
},
})
var sdkError *codersdk.Error
require.Error(t, err)
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
require.Equal(t, http.StatusForbidden, sdkError.StatusCode())
require.NoError(t, err)
require.Len(t, alogs.AuditLogs, 1)

// Using the organization selector allows the org admin to fetch audit logs
alogs, err:= orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
alogs, err = orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
SearchQuery: fmt.Sprintf("organization:%s", owner.OrganizationID.String()),
Pagination: codersdk.Pagination{
Limit: 5,
Expand Down
22 changes: 8 additions & 14 deletionscoderd/database/dbauthz/dbauthz.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -1248,22 +1248,12 @@ func (q *querier) GetApplicationName(ctx context.Context) (string, error) {
}

func (q *querier) GetAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) {
// To optimize the authz checks for audit logs, do not run an authorize
// check on each individual audit log row. In practice, audit logs are either
// fetched from a global or an organization scope.
// Applying a SQL filter would slow down the query for no benefit on how this query is
// actually used.

object := rbac.ResourceAuditLog
if arg.OrganizationID != uuid.Nil {
object = object.InOrg(arg.OrganizationID)
}

if err := q.authorizeContext(ctx, policy.ActionRead, object); err != nil {
return nil, err
prep, err := prepareSQLFilter(ctx, q.auth, policy.ActionRead, rbac.ResourceAuditLog.Type)
if err != nil {
return nil, xerrors.Errorf("(dev error) prepare sql filter: %w", err)
}

return q.db.GetAuditLogsOffset(ctx, arg)
return q.db.GetAuthorizedAuditLogsOffset(ctx, arg, prep)
}

func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (database.GetAuthorizationUserRolesRow, error) {
Expand DownExpand Up@@ -3852,3 +3842,7 @@ func (q *querier) GetAuthorizedUsers(ctx context.Context, arg database.GetUsersP
// GetUsers is authenticated.
return q.GetUsers(ctx, arg)
}

func (q *querier) GetAuthorizedAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams, _ rbac.PreparedAuthorized) ([]database.GetAuditLogsOffsetRow, error) {
return q.GetAuditLogsOffset(ctx, arg)
}
9 changes: 8 additions & 1 deletioncoderd/database/dbauthz/dbauthz_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -266,7 +266,14 @@ func (s *MethodTestSuite) TestAuditLogs() {
_ = dbgen.AuditLog(s.T(), db, database.AuditLog{})
check.Args(database.GetAuditLogsOffsetParams{
LimitOpt: 10,
}).Asserts(rbac.ResourceAuditLog, policy.ActionRead)
}).Asserts()
}))
s.Run("GetAuthorizedAuditLogsOffset", s.Subtest(func(db database.Store, check *expects) {
_ = dbgen.AuditLog(s.T(), db, database.AuditLog{})
_ = dbgen.AuditLog(s.T(), db, database.AuditLog{})
check.Args(database.GetAuditLogsOffsetParams{
LimitOpt: 10,
}, emptyPreparedAuthorized{}).Asserts()
}))
}

Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp