package oci import ( "context" "encoding/json" "fmt" "net" "sort" "time" "github.com/oracle/oci-go-sdk/v65/audit" "github.com/oracle/oci-go-sdk/v65/common" ) // maxAuditPages 限制单次查询的翻页数:繁忙租户单日事件可上千, // 到限即返回 Truncated=true,由调用方收窄时间窗。 // 默认过滤(噪声事件/内网发起)后有效结果变少,页数放宽到 10 缓解截断。 const maxAuditPages = 10 // AuditEvent 是审计事件的列表精简视图;EventId 为 CloudEvents 全局唯一 id, // 详情反查的键。Raw 为 SDK 原始事件的 JSON 序列化,由 service 层剥离进缓存, // 列表响应不再携带(详情接口按 eventId 取回)。 type AuditEvent struct { EventId string `json:"eventId"` EventTime *time.Time `json:"eventTime"` EventName string `json:"eventName"` Source string `json:"source"` ResourceName string `json:"resourceName"` CompartmentName string `json:"compartmentName"` PrincipalName string `json:"principalName"` IPAddress string `json:"ipAddress"` Status string `json:"status"` RequestAction string `json:"requestAction"` RequestPath string `json:"requestPath"` Raw json.RawMessage `json:"raw,omitempty"` } // AuditEventsResult 是一次审计查询的结果;Truncated 表示翻页到限被截断, // 此时 NextPage 携带 opc-next-page 游标,同一时间窗回传可断点续翻。 type AuditEventsResult struct { Items []AuditEvent `json:"items"` Truncated bool `json:"truncated"` NextPage string `json:"nextPage,omitempty"` } func (c *RealClient) auditClient(cred Credentials, region string) (audit.AuditClient, error) { ac, err := audit.NewAuditClientWithConfigurationProvider(provider(cred)) if err != nil { return ac, fmt.Errorf("new audit client: %w", err) } applyProxy(&ac.BaseClient, cred) if region != "" { ac.SetRegion(normalizeRegion(region)) } return ac, nil } // ListAuditEvents 实现 Client:实时查询租户根 compartment 在 [start, end) 内的 // 审计事件,最多翻 maxAuditPages 页,结果按发生时间倒序;纯读不落库。 // page 非空时从该游标断点续翻(必须配同一时间窗);到限截断时回传 NextPage。 func (c *RealClient) ListAuditEvents(ctx context.Context, cred Credentials, region string, start, end time.Time, page string) (AuditEventsResult, error) { ac, err := c.auditClient(cred, region) if err != nil { return AuditEventsResult{}, err } // Audit API 只接受分钟粒度:起止时间的秒与毫秒必须为 0 req := audit.ListEventsRequest{ CompartmentId: &cred.TenancyOCID, StartTime: &common.SDKTime{Time: start.UTC().Truncate(time.Minute)}, EndTime: &common.SDKTime{Time: end.UTC().Truncate(time.Minute)}, } if page != "" { req.Page = &page } result := AuditEventsResult{Items: []AuditEvent{}} for i := 0; i < maxAuditPages; i++ { resp, err := ac.ListEvents(ctx, req) if err != nil { return AuditEventsResult{}, fmt.Errorf("list audit events: %w", err) } appendAuditEvents(&result, resp.Items) if resp.OpcNextPage == nil { sortAuditEvents(result.Items) return result, nil } req.Page = resp.OpcNextPage } result.Truncated = true result.NextPage = deref(req.Page) sortAuditEvents(result.Items) return result, nil } // appendAuditEvents 过滤噪声后追加一页事件;原始事件只对保留条目序列化。 func appendAuditEvents(result *AuditEventsResult, items []audit.AuditEvent) { for _, ev := range items { out := toAuditEvent(ev) if !keepAuditEvent(out) { continue } if raw, mErr := json.Marshal(ev); mErr == nil { out.Raw = raw } result.Items = append(result.Items, out) } } // auditInternalCIDRs 是 OCI 服务内部互调的发起方网段(RFC1918 + CGNAT)。 var auditInternalCIDRs = func() []*net.IPNet { out := make([]*net.IPNet, 0, 4) for _, cidr := range []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/10"} { _, block, _ := net.ParseCIDR(cidr) out = append(out, block) } return out }() // keepAuditEvent 保留有展示价值的事件:Audit API 无服务端过滤参数(仅时间窗), // 在翻页循环内排除高频遥测噪声(SummarizeMetricsData)与内网地址发起的服务互调; // 无 IP 的事件(控制面内部)保留。 func keepAuditEvent(ev AuditEvent) bool { if ev.EventName == "SummarizeMetricsData" { return false } if ev.IPAddress == "" { return true } ip := net.ParseIP(ev.IPAddress) if ip == nil { return true } for _, block := range auditInternalCIDRs { if block.Contains(ip) { return false } } return true } // toAuditEvent 把 SDK 审计事件压平为列表 DTO;SDK 字段全为指针,逐层判 nil。 func toAuditEvent(ev audit.AuditEvent) AuditEvent { out := AuditEvent{EventId: deref(ev.EventId), Source: deref(ev.Source)} if ev.EventTime != nil { out.EventTime = &ev.EventTime.Time } if ev.Data == nil { return out } out.EventName = deref(ev.Data.EventName) out.ResourceName = deref(ev.Data.ResourceName) out.CompartmentName = deref(ev.Data.CompartmentName) if id := ev.Data.Identity; id != nil { out.PrincipalName = deref(id.PrincipalName) out.IPAddress = deref(id.IpAddress) } if req := ev.Data.Request; req != nil { out.RequestAction = deref(req.Action) out.RequestPath = deref(req.Path) } if resp := ev.Data.Response; resp != nil { out.Status = deref(resp.Status) } return out } // sortAuditEvents 按发生时间倒序排列;服务端返回顺序不保证,nil 时间排最后。 func sortAuditEvents(items []AuditEvent) { sort.SliceStable(items, func(i, j int) bool { ti, tj := items[i].EventTime, items[j].EventTime switch { case ti == nil: return false case tj == nil: return true default: return ti.After(*tj) } }) }