Add build number to workspace_build audit logs (#5267)

* got links working

* added translations

* fixed translation

* added translation for unavailable ip

* added support for group, template, user links

* cleaned up string

* added deleted label

* querying for workspace id

* remove prints

* fix/write tests

* added build number

* checking for existence of additional fields

* adjust documentation

* PR feedback
This commit is contained in:
Kira Pilot 2022-12-06 13:33:21 -05:00 committed by GitHub
parent 6651c1632d
commit df389d429c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 31 deletions

View File

@ -160,6 +160,11 @@ func (api *API) convertAuditLogs(ctx context.Context, dblogs []database.GetAudit
return alogs
}
type AdditionalFields struct {
WorkspaceName string
BuildNumber string
}
func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog {
ip, _ := netip.AddrFromSlice(dblog.Ip.IPNet.IP)
@ -185,12 +190,29 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
}
}
isDeleted := api.auditLogIsResourceDeleted(ctx, dblog)
var resourceLink string
var (
additionalFieldsBytes = []byte(dblog.AdditionalFields)
additionalFields AdditionalFields
err = json.Unmarshal(additionalFieldsBytes, &additionalFields)
)
if err != nil {
api.Logger.Error(ctx, "unmarshal additional fields", slog.Error(err))
resourceInfo := map[string]string{
"workspaceName": "unknown",
"buildNumber": "unknown",
}
dblog.AdditionalFields, err = json.Marshal(resourceInfo)
api.Logger.Error(ctx, "marshal additional fields", slog.Error(err))
}
var (
isDeleted = api.auditLogIsResourceDeleted(ctx, dblog)
resourceLink string
)
if isDeleted {
resourceLink = ""
} else {
resourceLink = api.auditLogResourceLink(ctx, dblog)
resourceLink = auditLogResourceLink(dblog, additionalFields)
}
return codersdk.AuditLog{
@ -209,23 +231,28 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
StatusCode: dblog.StatusCode,
AdditionalFields: dblog.AdditionalFields,
User: user,
Description: auditLogDescription(dblog),
Description: auditLogDescription(dblog, additionalFields),
ResourceLink: resourceLink,
IsDeleted: isDeleted,
}
}
func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
func auditLogDescription(alog database.GetAuditLogsOffsetRow, additionalFields AdditionalFields) string {
str := fmt.Sprintf("{user} %s",
codersdk.AuditAction(alog.Action).FriendlyString(),
)
// Strings for starting/stopping workspace builds follow the below format:
// "{user} started build for workspace {target}"
// "{user} started build #{build_number} for workspace {target}"
// where target is a workspace instead of a workspace build
// passed in on the FE via AuditLog.AdditionalFields rather than derived in request.go:35
if alog.ResourceType == database.ResourceTypeWorkspaceBuild && alog.Action != database.AuditActionDelete {
str += " build for"
if len(additionalFields.BuildNumber) == 0 {
str += " build for"
} else {
str += fmt.Sprintf(" build #%s for",
additionalFields.BuildNumber)
}
}
// We don't display the name (target) for git ssh keys. It's fairly long and doesn't
@ -295,12 +322,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
}
}
type AdditionalFields struct {
WorkspaceName string
BuildNumber string
}
func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAuditLogsOffsetRow) string {
func auditLogResourceLink(alog database.GetAuditLogsOffsetRow, additionalFields AdditionalFields) string {
switch alog.ResourceType {
case database.ResourceTypeTemplate:
return fmt.Sprintf("/templates/%s",
@ -312,11 +334,8 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit
return fmt.Sprintf("/@%s/%s",
alog.UserUsername.String, alog.ResourceTarget)
case database.ResourceTypeWorkspaceBuild:
additionalFieldsBytes := []byte(alog.AdditionalFields)
var additionalFields AdditionalFields
err := json.Unmarshal(additionalFieldsBytes, &additionalFields)
if err != nil {
api.Logger.Error(ctx, "unmarshal workspace name", slog.Error(err))
if len(additionalFields.WorkspaceName) == 0 || len(additionalFields.BuildNumber) == 0 {
return ""
}
return fmt.Sprintf("/@%s/%s/builds/%s",
alog.UserUsername.String, additionalFields.WorkspaceName, additionalFields.BuildNumber)

View File

@ -531,23 +531,23 @@ func (server *Server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*p
auditor := server.Auditor.Load()
build, getBuildErr := server.Database.GetWorkspaceBuildByJobID(ctx, job.ID)
if getBuildErr != nil {
server.Logger.Error(ctx, "failed to create audit log - get build err", slog.Error(err))
server.Logger.Error(ctx, "audit log - get build", slog.Error(err))
} else {
auditAction := auditActionFromTransition(build.Transition)
workspace, getWorkspaceErr := server.Database.GetWorkspaceByID(ctx, build.WorkspaceID)
if getWorkspaceErr != nil {
server.Logger.Error(ctx, "failed to create audit log - get workspace err", slog.Error(err))
server.Logger.Error(ctx, "audit log - get workspace", slog.Error(err))
} else {
// We pass the below information to the Auditor so that it
// can form a friendly string for the user to view in the UI.
workspaceResourceInfo := map[string]string{
buildResourceInfo := map[string]string{
"workspaceName": workspace.Name,
"buildNumber": strconv.FormatInt(int64(build.BuildNumber), 10),
}
wriBytes, err := json.Marshal(workspaceResourceInfo)
wriBytes, err := json.Marshal(buildResourceInfo)
if err != nil {
server.Logger.Error(ctx, "could not marshal workspace name", slog.Error(err))
server.Logger.Error(ctx, "marshal workspace resource info for failed job", slog.Error(err))
}
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuild]{
@ -756,14 +756,14 @@ func (server *Server) CompleteJob(ctx context.Context, completed *proto.Complete
// We pass the below information to the Auditor so that it
// can form a friendly string for the user to view in the UI.
workspaceResourceInfo := map[string]string{
buildResourceInfo := map[string]string{
"workspaceName": workspace.Name,
"buildNumber": strconv.FormatInt(int64(workspaceBuild.BuildNumber), 10),
}
wriBytes, err := json.Marshal(workspaceResourceInfo)
wriBytes, err := json.Marshal(buildResourceInfo)
if err != nil {
server.Logger.Error(ctx, "marshal resource info", slog.Error(err))
server.Logger.Error(ctx, "marshal resource info for successful job", slog.Error(err))
}
audit.BuildAudit(ctx, &audit.BuildAuditParams[database.WorkspaceBuild]{

View File

@ -11,7 +11,7 @@ We track **create, update and delete** events for the following resources:
- Template
- TemplateVersion
- Workspace
- Workspace start/stop
- WorkspaceBuild
- User
- Group

View File

@ -14,10 +14,7 @@ export const AuditLogDescription: FC<{ auditLog: AuditLog }> = ({
let target = auditLog.resource_target.trim()
// audit logs with a resource_type of workspace build use workspace name as a target
if (
auditLog.resource_type === "workspace_build" &&
auditLog.additional_fields.workspaceName
) {
if (auditLog.resource_type === "workspace_build") {
target = auditLog.additional_fields.workspaceName.trim()
}