初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
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)
}
})
}