全局审计完成
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m31s

This commit is contained in:
2026-08-07 11:02:52 +08:00
parent 88cc5e96ec
commit c64f3d8b80
94 changed files with 8641 additions and 6714 deletions

View File

@@ -27,31 +27,31 @@ type SubjectActivityFilter struct {
// SubjectActivityPage 是不包含平台调查字段的代理资源活动分页结果。
type SubjectActivityPage struct {
Resource SubjectResourceSummary `json:"resource"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
Items []SubjectActivity `json:"items"`
Retention retentionquery.Info `json:"retention"`
Resource SubjectResourceSummary `json:"resource" description:"已完成授权校验的目标资源"`
Total int64 `json:"total" description:"主体可见活动总数"`
Page int `json:"page" description:"当前页码"`
PageSize int `json:"page_size" description:"每页数量"`
Items []SubjectActivity `json:"items" description:"不包含平台内部调查字段的安全活动列表"`
Retention retentionquery.Info `json:"retention" description:"在线查询留存边界"`
}
// SubjectActivity 是写入时已生成的主体安全活动投影。
type SubjectActivity struct {
ActionCode string `json:"action_code"`
ActionName string `json:"action_name"`
SubjectSummary string `json:"subject_summary"`
SubjectData map[string]any `json:"subject_data"`
Result string `json:"result"`
OccurredAt time.Time `json:"occurred_at"`
RelatedResources []SubjectResourceSummary `json:"related_resources"`
ActionCode string `json:"action_code" description:"稳定动作编码"`
ActionName string `json:"action_name" description:"action_code对应的中文展示名称"`
SubjectSummary string `json:"subject_summary" description:"写入时生成的主体安全摘要"`
SubjectData map[string]any `json:"subject_data" description:"写入时生成的主体安全业务字段不包含平台before/after或内部原因"`
Result string `json:"result" enum:"success,failed,denied,partial,unknown" description:"活动结果稳定编码"`
OccurredAt time.Time `json:"occurred_at" description:"业务事实发生时间"`
RelatedResources []SubjectResourceSummary `json:"related_resources" description:"当前主体授权范围内的相关资源摘要"`
}
// SubjectResourceSummary 是主体活动允许公开的资源摘要。
type SubjectResourceSummary struct {
ResourceType string `json:"resource_type"`
ResourceID string `json:"resource_id"`
ResourceKey string `json:"resource_key"`
DisplayName string `json:"display_name"`
ResourceType string `json:"resource_type" description:"资源类型稳定编码"`
ResourceID string `json:"resource_id" description:"资源内部稳定ID主体前端不据此调用平台审计接口"`
ResourceKey string `json:"resource_key" description:"资源业务稳定Key"`
DisplayName string `json:"display_name" description:"资源安全展示名称"`
}
type subjectTarget struct {
@@ -60,14 +60,15 @@ type subjectTarget struct {
}
type subjectActivityRow struct {
ID uint
ActionCode string
ActionName string
Result string
OccurredAt time.Time
SubjectSummary string
SubjectData datatypes.JSON
TargetResourceID uint
ID uint
ActionCode string
ActionName string
Result string
OccurredAt time.Time
SubjectVisibility string
SubjectSummary string
SubjectData datatypes.JSON
TargetResourceID uint
}
type subjectResourceAuthorizer func(context.Context, []model.AuditEventResource) (map[string]bool, error)
@@ -78,9 +79,12 @@ func (q *Query) AgentResourceActivities(ctx context.Context, filter SubjectActiv
if err != nil {
return nil, err
}
if filter.Identifier == "" || !agentActivityResourceType(filter.ResourceType) || filter.Page < 0 || filter.PageSize < 0 || filter.PageSize > constants.MaxPageSize {
if filter.Identifier == "" || filter.Page < 0 || filter.PageSize < 0 || filter.PageSize > constants.MaxPageSize {
return nil, errors.New(errors.CodeInvalidParam)
}
if !agentActivityResourceType(filter.ResourceType) {
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
filter.Page, filter.PageSize = normalizePage(filter.Page, filter.PageSize)
retention, err := retentionquery.Load(ctx, q.db, retentionquery.SourceAudit)
if err != nil {
@@ -108,9 +112,12 @@ func (q *Query) EnterpriseResourceActivities(ctx context.Context, filter Subject
if enterpriseID == 0 {
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
if filter.Identifier == "" || !enterpriseActivityResourceType(filter.ResourceType) || filter.Page < 0 || filter.PageSize < 0 || filter.PageSize > constants.MaxPageSize {
if filter.Identifier == "" || filter.Page < 0 || filter.PageSize < 0 || filter.PageSize > constants.MaxPageSize {
return nil, errors.New(errors.CodeInvalidParam)
}
if !enterpriseActivityResourceType(filter.ResourceType) {
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
filter.Page, filter.PageSize = normalizePage(filter.Page, filter.PageSize)
retention, err := retentionquery.Load(ctx, q.db, retentionquery.SourceAudit)
if err != nil {
@@ -142,13 +149,13 @@ func (q *Query) subjectActivitiesForTarget(ctx context.Context, filter SubjectAc
}
rows := make([]subjectActivityRow, 0, filter.PageSize)
if err := base.Select("tb_audit_event.id, action_code, action_name, result, occurred_at, target.subject_summary, target.subject_data, target.id AS target_resource_id").
if err := base.Select("tb_audit_event.id, action_code, action_name, result, occurred_at, target.subject_visibility, target.subject_summary, target.subject_data, target.id AS target_resource_id").
Joins("JOIN tb_audit_event_resource AS target ON target.audit_event_id = tb_audit_event.id AND target.resource_type = ? AND target.resource_id = ?", filter.ResourceType, target.id).
Where("target.subject_visibility IN ?", []string{constants.AuditSubjectResult, constants.AuditSubjectDetail}).
Order("occurred_at DESC, tb_audit_event.id DESC").Offset((filter.Page - 1) * filter.PageSize).Limit(filter.PageSize).Scan(&rows).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询代理资源活动失败")
}
items, err := q.projectSubjectActivities(ctx, rows, authorize)
items, err := q.projectSubjectActivities(ctx, rows, target, authorize)
if err != nil {
return nil, err
}
@@ -265,24 +272,28 @@ func newSubjectTarget(resourceType string, id uint, key, name string) subjectTar
return subjectTarget{summary: SubjectResourceSummary{ResourceType: resourceType, ResourceID: resourceID, ResourceKey: key, DisplayName: name}, id: resourceID}
}
func (q *Query) projectSubjectActivities(ctx context.Context, rows []subjectActivityRow, authorize subjectResourceAuthorizer) ([]SubjectActivity, error) {
func (q *Query) projectSubjectActivities(ctx context.Context, rows []subjectActivityRow, target subjectTarget, authorize subjectResourceAuthorizer) ([]SubjectActivity, error) {
items := make([]SubjectActivity, 0, len(rows))
if len(rows) == 0 {
return items, nil
}
eventIDs := make([]uint, 0, len(rows))
for _, row := range rows {
eventIDs = append(eventIDs, row.ID)
}
var resources []model.AuditEventResource
if err := q.db.WithContext(ctx).Where("audit_event_id IN ? AND subject_visibility IN ?", eventIDs, []string{constants.AuditSubjectResult, constants.AuditSubjectDetail}).
Order("audit_event_id ASC, sort_order ASC, id ASC").Find(&resources).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询主体可见关联资源失败")
resources := make([]model.AuditEventResource, 0)
if len(eventIDs) > 0 {
if err := q.db.WithContext(ctx).Where("audit_event_id IN ? AND subject_visibility IN ?", eventIDs, []string{constants.AuditSubjectResult, constants.AuditSubjectDetail}).
Order("audit_event_id ASC, sort_order ASC, id ASC").Find(&resources).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询主体可见关联资源失败")
}
}
allowed, err := authorize(ctx, resources)
targetResourceID := target.id
authorizationResources := append(resources, model.AuditEventResource{ResourceType: target.summary.ResourceType, ResourceID: &targetResourceID})
allowed, err := authorize(ctx, authorizationResources)
if err != nil {
return nil, err
}
if !allowed[resourceAccessKey(target.summary.ResourceType, target.id)] {
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
}
related := make(map[uint][]SubjectResourceSummary, len(rows))
for _, resource := range resources {
if resource.ResourceID == nil || !allowed[resourceAccessKey(resource.ResourceType, *resource.ResourceID)] {
@@ -294,9 +305,12 @@ func (q *Query) projectSubjectActivities(ctx context.Context, rows []subjectActi
})
}
for _, row := range rows {
data, err := decodeObject(row.SubjectData)
if err != nil {
return nil, err
data := map[string]any{}
if row.SubjectVisibility == constants.AuditSubjectDetail {
data, err = decodeObject(row.SubjectData)
if err != nil {
return nil, err
}
}
items = append(items, SubjectActivity{ActionCode: row.ActionCode, ActionName: row.ActionName,
SubjectSummary: row.SubjectSummary, SubjectData: data, Result: row.Result,