补充审计中间件

This commit is contained in:
yumaojun03 2025-06-15 16:35:05 +08:00
parent e1615df11f
commit 9171077fb9
13 changed files with 210 additions and 37 deletions

View File

@ -1,2 +1,21 @@
# 操作审计
1. 路有装饰, 路有配置
```go
// required_auth=true/false
ws.Route(ws.GET("").To(h.QueryUser).
Doc("用户列表查询").
Metadata(restfulspec.KeyOpenAPITags, tags).
// 这个开关怎么生效
// 中间件需求读取接口的描述信息,来决定是否需要认证
Metadata(permission.Auth(true)).
Metadata(permission.Permission(true)).
Metadata(permission.Resource("user")).
Metadata(permission.Action("list")).
// 开启接口操作审计
Metadata(audit.Enable(true)).
Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")).
Param(restful.QueryParameter("page_number", "页码").DataType("integer")).
Writes(Set{}).
Returns(200, "OK", Set{}))
```

View File

@ -4,7 +4,8 @@ import (
"context"
"io"
"github.com/infraboard/modules/maudit/apps/event"
"122.51.31.227/go-course/go18/devcloud/audit/apps/event"
"github.com/infraboard/mcube/v2/types"
)
// 读取消息,处理消息, 使用同步方法, 会阻塞
@ -27,7 +28,7 @@ func (c *consumer) Run(ctx context.Context) error {
// 发送的数据时Json格式, 接收用的JSON, 发送也需要使用JSON
err = e.Load(m.Value)
if err == nil {
if err := event.GetService().SaveEvent(ctx, event.NewEventSet().Add(e)); err != nil {
if err := event.GetService().SaveEvent(ctx, types.NewSet[*event.Event]().Add(e)); err != nil {
c.log.Error().Msgf("save event error, %s", err)
}
}

View File

@ -3,6 +3,7 @@ package consumer
import (
"context"
"122.51.31.227/go-course/go18/devcloud/audit/apps/event"
"github.com/infraboard/mcube/v2/ioc"
"github.com/infraboard/mcube/v2/ioc/config/log"
"github.com/rs/zerolog"
@ -53,6 +54,10 @@ func (i *consumer) Init() error {
return nil
}
func (i *consumer) Priority() int {
return event.PRIORITY - 1
}
func (i *consumer) Close(ctx context.Context) error {
i.ctx.Done()
return nil

View File

@ -8,12 +8,17 @@ import (
ioc_mongo "github.com/infraboard/mcube/v2/ioc/config/mongo"
"go.mongodb.org/mongo-driver/mongo"
// 引入消费者
_ "122.51.31.227/go-course/go18/devcloud/audit/apps/event/consumer"
)
func init() {
ioc.Controller().Registry(&EventServiceImpl{})
}
var _ event.Service = (*EventServiceImpl)(nil)
// 业务具体实现
type EventServiceImpl struct {
// 继承模版
@ -31,6 +36,10 @@ func (i *EventServiceImpl) Name() string {
return event.AppName
}
func (i *EventServiceImpl) Priority() int {
return event.PRIORITY
}
// 初始化
func (i *EventServiceImpl) Init() error {
// 对象

View File

@ -35,6 +35,8 @@ type Event struct {
// 做了什么操作, 服务:资源:动作
// 服务 <cmdb, mcenter, ....>
Service string `json:"service" bson:"service"`
// 哪个空间
Namespace string `json:"namespace" bson:"namespace"`
// 资源 <secret, user, namespace, ...>
ResourceType string `json:"resource_type" bson:"resource_type"`
// 动作 <list, get, update, create, delete, ....>

View File

@ -0,0 +1,5 @@
package event
const (
PRIORITY = 90
)

View File

@ -0,0 +1,9 @@
package audit
const (
META_AUDIT_KEY = "audit"
)
func Enable(v bool) (string, bool) {
return META_AUDIT_KEY, v
}

View File

@ -1 +1,46 @@
package audit
import (
"122.51.31.227/go-course/go18/devcloud/mcenter/permission"
"github.com/infraboard/mcube/v2/ioc"
"github.com/infraboard/mcube/v2/ioc/config/gorestful"
ioc_kafka "github.com/infraboard/mcube/v2/ioc/config/kafka"
"github.com/infraboard/mcube/v2/ioc/config/log"
"github.com/rs/zerolog"
"github.com/segmentio/kafka-go"
)
func init() {
ioc.Config().Registry(&EventSender{
Topic: "audit_go18",
})
}
// 审计中间件
type EventSender struct {
ioc.ObjectImpl
log *zerolog.Logger
// 当前这个消费者 配置的topic
Topic string `toml:"topic" json:"topic" yaml:"topic" env:"TOPIC"`
//
wirter *kafka.Writer
}
// 中间件对象名称
func (c *EventSender) Name() string {
return "audit_middleware"
}
func (c *EventSender) Priority() int {
return permission.GetCheckerPriority() - 1
}
func (c *EventSender) Init() error {
c.log = log.Sub(c.Name())
c.wirter = ioc_kafka.Producer(c.Topic)
// 注册认证中间件
gorestful.RootRouter().Filter(c.SendEvent())
return nil
}

View File

@ -0,0 +1,82 @@
package audit
import (
"context"
"122.51.31.227/go-course/go18/devcloud/audit/apps/event"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/endpoint"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/token"
"github.com/emicklei/go-restful/v3"
"github.com/infraboard/mcube/v2/ioc/config/application"
)
// 审计日志的发送逻辑
func (a *EventSender) SendEvent() restful.FilterFunction {
return func(req *restful.Request, resp *restful.Response, fc *restful.FilterChain) {
sr := req.SelectedRoute()
md := NewMetaData(sr.Metadata())
// 开关打开,则开启审计
if md.GetBool(META_AUDIT_KEY) {
// 获取当前是否需要审计
e := event.NewEvent()
// 用户信息
tk := token.GetTokenFromCtx(req.Request.Context())
if tk != nil {
e.Who = tk.UserName
e.Namespace = tk.NamespaceName
}
// ioc 里面获取当前应用的名称
e.Service = application.Get().AppName
e.ResourceType = md.GetString(endpoint.META_RESOURCE_KEY)
e.Action = md.GetString(endpoint.META_ACTION_KEY)
// {id} /:id
e.ResourceId = req.PathParameter("id")
e.UserAgent = req.Request.UserAgent()
e.Extras["method"] = sr.Method()
e.Extras["path"] = sr.Path()
e.Extras["operation"] = sr.Operation()
// 补充处理后的数据
e.StatusCode = resp.StatusCode()
// 发送给topic, 使用这个中间件的使用者需要配置kafka
err := a.wirter.WriteMessages(context.Background(), e.ToKafkaMessage())
if err != nil {
a.log.Error().Msgf("send message error, %s", err)
} else {
a.log.Debug().Msgf("send audit event ok, who: %s, resource: %s, action: %s", e.Who, e.ResourceType, e.Action)
}
}
// 路有给后续逻辑
fc.ProcessFilter(req, resp)
}
}
func NewMetaData(data map[string]any) *MetaData {
return &MetaData{
data: data,
}
}
type MetaData struct {
data map[string]any
}
func (m *MetaData) GetString(key string) string {
if v, ok := m.data[key]; ok {
return v.(string)
}
return ""
}
func (m *MetaData) GetBool(key string) bool {
if v, ok := m.data[key]; ok {
return v.(bool)
}
return false
}

View File

@ -1,91 +1,80 @@
<mxfile host="65bd71144e">
<diagram id="Z87e6aYbOS6UnjUWpnIF" name="第 1 页">
<mxGraphModel dx="888" dy="530" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<mxGraphModel dx="888" dy="392" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="2" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxCell id="2" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="110" y="140" width="480" height="230" as="geometry"/>
</mxCell>
<mxCell id="3" value="audit" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxCell id="3" value="audit" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="110" y="140" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="29" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1" source="4" target="28">
<mxCell id="29" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="4" target="28" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="4" value="鉴权中间件" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxCell id="4" value="鉴权中间件" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="420" y="160" width="120" height="40" as="geometry"/>
</mxCell>
<mxCell id="8" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="5" target="4">
<mxCell id="8" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="5" target="4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="5" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxCell id="5" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
<mxGeometry x="580" y="20" width="30" height="60" as="geometry"/>
</mxCell>
<mxCell id="6" value="业务资源" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxCell id="6" value="业务资源" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="420" y="290" width="120" height="70" as="geometry"/>
</mxCell>
<mxCell id="10" value="操作日志&lt;div&gt;保存和查看&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxCell id="10" value="操作日志&lt;div&gt;保存和查看&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="140" y="165" width="160" height="155" as="geometry"/>
</mxCell>
<mxCell id="15" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="13" target="10">
<mxCell id="15" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.3333333333333333;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="13" target="10" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="13" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="1">
<mxCell id="13" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
<mxGeometry x="40" y="20" width="30" height="60" as="geometry"/>
</mxCell>
<mxCell id="14" value="管理员" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
<mxCell id="14" value="管理员" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="25" y="110" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="16" value="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;接口设计 (同步/异步)&lt;/h1&gt;&lt;p&gt;日志功能不能影响 业务功能,因此日志提供的保存是异步&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" vertex="1" parent="1">
<mxCell id="16" value="&lt;h1 style=&quot;margin-top: 0px;&quot;&gt;接口设计 (同步/异步)&lt;/h1&gt;&lt;p&gt;日志功能不能影响 业务功能,因此日志提供的保存是异步&lt;/p&gt;&lt;p&gt;&lt;br&gt;&lt;/p&gt;" style="text;html=1;whiteSpace=wrap;overflow=hidden;rounded=0;" parent="1" vertex="1">
<mxGeometry x="40" y="510" width="250" height="100" as="geometry"/>
</mxCell>
<mxCell id="17" value="DB" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.database;whiteSpace=wrap;" vertex="1" parent="1">
<mxCell id="17" value="DB" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.database;whiteSpace=wrap;" parent="1" vertex="1">
<mxGeometry x="60" y="440" width="60" height="50" as="geometry"/>
</mxCell>
<mxCell id="18" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="10" target="17">
<mxCell id="18" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="10" target="17" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="21" value="kafka" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.kafka;" vertex="1" parent="1">
<mxCell id="21" value="kafka" style="points=[];aspect=fixed;html=1;align=center;shadow=0;dashed=0;fillColor=#FF6A00;strokeColor=none;shape=mxgraph.alibaba_cloud.kafka;" parent="1" vertex="1">
<mxGeometry x="340" y="444" width="56.699999999999996" height="42" as="geometry"/>
</mxCell>
<mxCell id="22" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.75;exitY=1;exitDx=0;exitDy=0;entryX=0.656;entryY=0.161;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="10" target="21">
<mxGeometry relative="1" as="geometry"/>
<mxCell id="24" value="&lt;span style=&quot;color: rgb(0, 0, 0); font-size: 11px; text-wrap-mode: nowrap; background-color: rgb(255, 255, 255);&quot;&gt;receiver&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="220" y="280" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="26" value="send" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="22">
<mxGeometry x="0.0019" y="-3" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="23" value="sender" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="230" y="280" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="24" value="&lt;span style=&quot;color: rgb(0, 0, 0); font-size: 11px; text-wrap-mode: nowrap; background-color: rgb(255, 255, 255);&quot;&gt;receiver&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="150" y="280" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="25" value="" style="endArrow=classic;html=1;exitX=-0.015;exitY=0.494;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;edgeStyle=orthogonalEdgeStyle;" edge="1" parent="1" source="21" target="24">
<mxCell id="25" value="" style="endArrow=classic;html=1;exitX=-0.015;exitY=0.494;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.75;entryY=1;entryDx=0;entryDy=0;edgeStyle=orthogonalEdgeStyle;" parent="1" source="21" target="24" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="190" y="430" as="sourcePoint"/>
<mxPoint x="240" y="380" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="27" value="receive" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="25">
<mxCell id="27" value="receive" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="25" vertex="1" connectable="0">
<mxGeometry x="-0.2688" y="2" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="31" style="edgeStyle=none;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="1" source="28" target="10">
<mxCell id="31" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;entryX=0.664;entryY=0.129;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="28" target="21" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="32" value="操作日志" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="31">
<mxCell id="32" value="操作日志" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="31" vertex="1" connectable="0">
<mxGeometry x="-0.1026" y="1" relative="1" as="geometry">
<mxPoint as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="33" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="28" target="6">
<mxCell id="33" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="28" target="6" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="28" value="审计中间件" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxCell id="28" value="审计中间件" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="420" y="220" width="120" height="40" as="geometry"/>
</mxCell>
</root>

View File

@ -18,6 +18,7 @@
endpoints = ["127.0.0.1:27017"]
username = ""
password = ""
database = "devcloud_go18"
[kafka]
brokers = ["127.0.0.1:9092"]

View File

@ -1,6 +1,7 @@
package api
import (
"122.51.31.227/go-course/go18/devcloud/audit/audit"
"122.51.31.227/go-course/go18/devcloud/mcenter/apps/user"
"122.51.31.227/go-course/go18/devcloud/mcenter/permission"
"github.com/infraboard/mcube/v2/ioc"
@ -40,6 +41,7 @@ func (h *UserRestfulApiHandler) Init() error {
Metadata(permission.Permission(true)).
Metadata(permission.Resource("user")).
Metadata(permission.Action("list")).
Metadata(audit.Enable(true)).
Param(restful.QueryParameter("page_size", "分页大小").DataType("integer")).
Param(restful.QueryParameter("page_number", "页码").DataType("integer")).
Writes(Set{}).

View File

@ -20,6 +20,10 @@ func init() {
ioc.Config().Registry(&Checker{})
}
func GetCheckerPriority() int {
return ioc.Config().Get("permission_checker").Priority()
}
func Auth(v bool) (string, bool) {
return endpoint.META_REQUIRED_AUTH_KEY, v
}