feat: add audit logs
This commit is contained in:
parent
50c3539cb0
commit
10c317f07c
|
@ -0,0 +1,29 @@
|
|||
/* Copyright © INFINI Ltd. All rights reserved.
|
||||
* Web: https://infinilabs.com
|
||||
* Email: hello#infini.ltd */
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"infini.sh/framework/core/api"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetClientIP 获取客户端 IP 地址
|
||||
func GetClientIP(req *http.Request) string {
|
||||
h := new(api.Handler)
|
||||
// 1. 尝试从 XFF 头获取
|
||||
xff := h.GetHeader(req, "X-Forwarded-For", "")
|
||||
clientIP := strings.Split(xff, ",")[0]
|
||||
if len(clientIP) > 0 {
|
||||
return clientIP
|
||||
}
|
||||
// 2. 尝试从 X-Real-IP 头获取
|
||||
clientIP = h.GetHeader(req, "X-Real-IP", "")
|
||||
if len(clientIP) > 0 {
|
||||
return clientIP
|
||||
}
|
||||
// 3. 从 RemoteAddr 获取
|
||||
return strings.Split(req.RemoteAddr, ":")[0]
|
||||
}
|
|
@ -612,4 +612,109 @@ PUT $[[SETUP_INDEX_PREFIX]]activities-00001
|
|||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PUT _template/$[[SETUP_INDEX_PREFIX]]audit-logs-rollover
|
||||
{
|
||||
"order" : 100000,
|
||||
"index_patterns" : [
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs*"
|
||||
],
|
||||
"settings" : {
|
||||
"index" : {
|
||||
"format" : "7",
|
||||
"lifecycle" : {
|
||||
"name" : "ilm_$[[SETUP_INDEX_PREFIX]]metrics-30days-retention",
|
||||
"rollover_alias" : "$[[SETUP_INDEX_PREFIX]]audit-logs"
|
||||
},
|
||||
"codec" : "best_compression",
|
||||
"number_of_shards" : "1",
|
||||
"translog.durability":"async"
|
||||
}
|
||||
},
|
||||
"mappings" : {
|
||||
"dynamic_templates" : [
|
||||
{
|
||||
"strings" : {
|
||||
"mapping" : {
|
||||
"ignore_above" : 256,
|
||||
"type" : "keyword"
|
||||
},
|
||||
"match_mapping_type" : "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"aliases" : { }
|
||||
}
|
||||
|
||||
|
||||
PUT $[[SETUP_INDEX_PREFIX]]audit-logs-00001
|
||||
{
|
||||
"mappings": {
|
||||
"dynamic_templates": [
|
||||
{
|
||||
"strings": {
|
||||
"match_mapping_type": "string",
|
||||
"mapping": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"metadata": {
|
||||
"properties": {
|
||||
"operator": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"log_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"lifecycle.rollover_alias": "$[[SETUP_INDEX_PREFIX]]audit-logs",
|
||||
"refresh_interval": "5s",
|
||||
"mapping": {
|
||||
"total_fields": {
|
||||
"limit": "20000"
|
||||
}
|
||||
},
|
||||
"max_result_window": "10000000",
|
||||
"analysis": {
|
||||
"analyzer": {
|
||||
"suggest_text_search": {
|
||||
"filter": [
|
||||
"lowercase",
|
||||
"word_delimiter"
|
||||
],
|
||||
"tokenizer": "classic"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aliases": {
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs": {
|
||||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -612,4 +612,109 @@ PUT $[[SETUP_INDEX_PREFIX]]activities-00001
|
|||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PUT _template/$[[SETUP_INDEX_PREFIX]]audit-logs-rollover
|
||||
{
|
||||
"order" : 100000,
|
||||
"index_patterns" : [
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs*"
|
||||
],
|
||||
"settings" : {
|
||||
"index" : {
|
||||
"format" : "7",
|
||||
"lifecycle" : {
|
||||
"name" : "ilm_$[[SETUP_INDEX_PREFIX]]metrics-30days-retention",
|
||||
"rollover_alias" : "$[[SETUP_INDEX_PREFIX]]audit-logs"
|
||||
},
|
||||
"codec" : "best_compression",
|
||||
"number_of_shards" : "1",
|
||||
"translog.durability":"async"
|
||||
}
|
||||
},
|
||||
"mappings" : {
|
||||
"dynamic_templates" : [
|
||||
{
|
||||
"strings" : {
|
||||
"mapping" : {
|
||||
"ignore_above" : 256,
|
||||
"type" : "keyword"
|
||||
},
|
||||
"match_mapping_type" : "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"aliases" : { }
|
||||
}
|
||||
|
||||
|
||||
PUT $[[SETUP_INDEX_PREFIX]]audit-logs-00001
|
||||
{
|
||||
"mappings": {
|
||||
"dynamic_templates": [
|
||||
{
|
||||
"strings": {
|
||||
"match_mapping_type": "string",
|
||||
"mapping": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"metadata": {
|
||||
"properties": {
|
||||
"operator": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"log_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"lifecycle.rollover_alias": "$[[SETUP_INDEX_PREFIX]]audit-logs",
|
||||
"refresh_interval": "5s",
|
||||
"mapping": {
|
||||
"total_fields": {
|
||||
"limit": "20000"
|
||||
}
|
||||
},
|
||||
"max_result_window": "10000000",
|
||||
"analysis": {
|
||||
"analyzer": {
|
||||
"suggest_text_search": {
|
||||
"filter": [
|
||||
"lowercase",
|
||||
"word_delimiter"
|
||||
],
|
||||
"tokenizer": "classic"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aliases": {
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs": {
|
||||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -553,4 +553,106 @@ PUT $[[SETUP_INDEX_PREFIX]]activities-00001
|
|||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PUT _template/$[[SETUP_INDEX_PREFIX]]audit-logs-rollover
|
||||
{
|
||||
"order" : 100000,
|
||||
"template" : "$[[SETUP_INDEX_PREFIX]]audit-logs*",
|
||||
"settings" : {
|
||||
"index" : {
|
||||
"format" : "7",
|
||||
"codec" : "best_compression",
|
||||
"number_of_shards" : "1",
|
||||
"translog.durability":"async"
|
||||
}
|
||||
},
|
||||
"mappings" : {
|
||||
"doc":{
|
||||
"dynamic_templates" : [
|
||||
{
|
||||
"strings" : {
|
||||
"mapping" : {
|
||||
"ignore_above" : 256,
|
||||
"type" : "keyword"
|
||||
},
|
||||
"match_mapping_type" : "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"aliases" : { }
|
||||
}
|
||||
|
||||
|
||||
PUT $[[SETUP_INDEX_PREFIX]]audit-logs-00001
|
||||
{
|
||||
"mappings": {
|
||||
"doc":{
|
||||
"dynamic_templates": [
|
||||
{
|
||||
"strings": {
|
||||
"match_mapping_type": "string",
|
||||
"mapping": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"metadata": {
|
||||
"properties": {
|
||||
"operator": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"log_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"refresh_interval": "5s",
|
||||
"mapping": {
|
||||
"total_fields": {
|
||||
"limit": "20000"
|
||||
}
|
||||
},
|
||||
"max_result_window": "10000000",
|
||||
"analysis": {
|
||||
"analyzer": {
|
||||
"suggest_text_search": {
|
||||
"filter": [
|
||||
"lowercase",
|
||||
"word_delimiter"
|
||||
],
|
||||
"tokenizer": "classic"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aliases": {
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs": {
|
||||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -630,4 +630,113 @@ PUT $[[SETUP_INDEX_PREFIX]]activities-00001
|
|||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PUT _template/$[[SETUP_INDEX_PREFIX]]audit-logs-rollover
|
||||
{
|
||||
"order" : 100000,
|
||||
"index_patterns" : [
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs*"
|
||||
],
|
||||
"settings" : {
|
||||
"index" : {
|
||||
"format" : "7",
|
||||
"lifecycle" : {
|
||||
"name" : "ilm_$[[SETUP_INDEX_PREFIX]]metrics-30days-retention",
|
||||
"rollover_alias" : "$[[SETUP_INDEX_PREFIX]]audit-logs"
|
||||
},
|
||||
"codec" : "best_compression",
|
||||
"number_of_shards" : "1",
|
||||
"translog.durability":"async"
|
||||
}
|
||||
},
|
||||
"mappings" : {
|
||||
"doc":{
|
||||
"dynamic_templates" : [
|
||||
{
|
||||
"strings" : {
|
||||
"mapping" : {
|
||||
"ignore_above" : 256,
|
||||
"type" : "keyword"
|
||||
},
|
||||
"match_mapping_type" : "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"aliases" : { }
|
||||
}
|
||||
|
||||
|
||||
PUT $[[SETUP_INDEX_PREFIX]]audit-logs-00001
|
||||
{
|
||||
"mappings": {
|
||||
"doc":{
|
||||
"dynamic_templates": [
|
||||
{
|
||||
"strings": {
|
||||
"match_mapping_type": "string",
|
||||
"mapping": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"metadata": {
|
||||
"properties": {
|
||||
"operator": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"log_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"lifecycle.rollover_alias": "$[[SETUP_INDEX_PREFIX]]audit-logs",
|
||||
"refresh_interval": "5s",
|
||||
"mapping": {
|
||||
"total_fields": {
|
||||
"limit": "20000"
|
||||
}
|
||||
},
|
||||
"max_result_window": "10000000",
|
||||
"analysis": {
|
||||
"analyzer": {
|
||||
"suggest_text_search": {
|
||||
"filter": [
|
||||
"lowercase",
|
||||
"word_delimiter"
|
||||
],
|
||||
"tokenizer": "classic"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aliases": {
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs": {
|
||||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -597,6 +597,111 @@ PUT $[[SETUP_INDEX_PREFIX]]activities-00001
|
|||
}
|
||||
|
||||
POST _plugins/_ism/add/$[[SETUP_INDEX_PREFIX]]activities-00001
|
||||
{
|
||||
"policy_id": "ilm_$[[SETUP_INDEX_PREFIX]]metrics-30days-retention"
|
||||
}
|
||||
|
||||
|
||||
PUT /_template/$[[SETUP_INDEX_PREFIX]]audit-logs-rollover
|
||||
{
|
||||
"order": 100000,
|
||||
"index_patterns" : [
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs*"
|
||||
],
|
||||
"settings": {
|
||||
"plugins.index_state_management.rollover_alias": "$[[SETUP_INDEX_PREFIX]]audit-logs",
|
||||
"codec": "best_compression",
|
||||
"number_of_shards": "1",
|
||||
"translog": {
|
||||
"durability": "async"
|
||||
}
|
||||
},
|
||||
"mappings" : {
|
||||
"dynamic_templates" : [
|
||||
{
|
||||
"strings" : {
|
||||
"mapping" : {
|
||||
"ignore_above" : 256,
|
||||
"type" : "keyword"
|
||||
},
|
||||
"match_mapping_type" : "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"aliases" : { }
|
||||
}
|
||||
|
||||
|
||||
PUT $[[SETUP_INDEX_PREFIX]]audit-logs-00001
|
||||
{
|
||||
"mappings": {
|
||||
"dynamic_templates": [
|
||||
{
|
||||
"strings": {
|
||||
"match_mapping_type": "string",
|
||||
"mapping": {
|
||||
"ignore_above": 256,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"metadata": {
|
||||
"properties": {
|
||||
"operator": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"log_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
},
|
||||
"resource_type": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 256
|
||||
}
|
||||
}
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "date"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"index": {
|
||||
"refresh_interval": "5s",
|
||||
"mapping": {
|
||||
"total_fields": {
|
||||
"limit": "20000"
|
||||
}
|
||||
},
|
||||
"max_result_window": "10000000",
|
||||
"analysis": {
|
||||
"analyzer": {
|
||||
"suggest_text_search": {
|
||||
"filter": [
|
||||
"lowercase",
|
||||
"word_delimiter"
|
||||
],
|
||||
"tokenizer": "classic"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"aliases": {
|
||||
"$[[SETUP_INDEX_PREFIX]]audit-logs": {
|
||||
"is_write_index": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
POST _plugins/_ism/add/$[[SETUP_INDEX_PREFIX]]audit-logs-00001
|
||||
{
|
||||
"policy_id": "ilm_$[[SETUP_INDEX_PREFIX]]metrics-30days-retention"
|
||||
}
|
|
@ -92,6 +92,22 @@ pipeline:
|
|||
worker_size: 1
|
||||
bulk_size_in_kb: 1
|
||||
|
||||
- name: merge_audit_log
|
||||
auto_start: true
|
||||
keep_running: true
|
||||
processor:
|
||||
- indexing_merge:
|
||||
input_queue: "logging-audit-log-queue"
|
||||
idle_timeout_in_seconds: 1
|
||||
elasticsearch: "$[[CLUSTER_ID]]"
|
||||
index_name: "$[[INDEX_PREFIX]]audit-logs"
|
||||
output_queue:
|
||||
name: "pipeline-audit-logs"
|
||||
label:
|
||||
tag: "audit_log_logging"
|
||||
worker_size: 1
|
||||
bulk_size_in_kb: 1
|
||||
|
||||
- name: ingest_merged_requests
|
||||
auto_start: true
|
||||
keep_running: true
|
||||
|
|
8
main.go
8
main.go
|
@ -6,6 +6,8 @@ import (
|
|||
_ "expvar"
|
||||
api3 "infini.sh/console/modules/agent/api"
|
||||
"infini.sh/console/plugin/api/email"
|
||||
"infini.sh/console/plugin/audit_log"
|
||||
"infini.sh/framework/core/api"
|
||||
model2 "infini.sh/framework/core/model"
|
||||
_ "time/tzdata"
|
||||
|
||||
|
@ -59,7 +61,6 @@ func main() {
|
|||
app.Init(nil)
|
||||
defer app.Shutdown()
|
||||
|
||||
|
||||
modules := []module.ModuleItem{}
|
||||
modules = append(modules, module.ModuleItem{Value: &stats.SimpleStatsModule{}, Priority: 1})
|
||||
modules = append(modules, module.ModuleItem{Value: &elastic2.ElasticModule{}, Priority: 1})
|
||||
|
@ -90,7 +91,6 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
api3.Init()
|
||||
|
||||
appConfig = &config.AppConfig{
|
||||
|
@ -113,6 +113,8 @@ func main() {
|
|||
appUI = &UI{Config: appConfig}
|
||||
appUI.InitUI()
|
||||
|
||||
api.AddGlobalInterceptors(new(audit_log.MonitoringInterceptor))
|
||||
|
||||
}, func() {
|
||||
|
||||
module.Start()
|
||||
|
@ -138,7 +140,7 @@ func main() {
|
|||
orm.RegisterSchemaWithIndexName(model.EmailServer{}, "email-server")
|
||||
orm.RegisterSchemaWithIndexName(model2.Instance{}, "instance")
|
||||
orm.RegisterSchemaWithIndexName(api3.RemoteConfig{}, "configs")
|
||||
|
||||
orm.RegisterSchemaWithIndexName(model.AuditLog{}, "audit-logs")
|
||||
|
||||
if global.Env().SetupRequired() {
|
||||
for _, v := range modules {
|
||||
|
|
|
@ -0,0 +1,369 @@
|
|||
/* Copyright © INFINI Ltd. All rights reserved.
|
||||
* Web: https://infinilabs.com
|
||||
* Email: hello#infini.ltd */
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"infini.sh/framework/core/util"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 错误定义
|
||||
var (
|
||||
ErrCreatedTimeNotProvided = errors.New("created time not provided")
|
||||
ErrOperatorNotProvided = errors.New("operator not provided")
|
||||
ErrLogTypeNotProvided = errors.New("log type not provided or invalid")
|
||||
ErrResourceTypeNotProvided = errors.New("resource type not provided or invalid")
|
||||
ErrEventNotProvided = errors.New("event not provided")
|
||||
ErrEventSourceIPNotProvided = errors.New("event source ip not provided or invalid")
|
||||
ErrOperationNotProvided = errors.New("operation not provided or invalid")
|
||||
)
|
||||
|
||||
const (
|
||||
// LogTypeOperation 表示操作日志
|
||||
LogTypeOperation = "operation"
|
||||
// LogTypeAccess 表示访问日志
|
||||
LogTypeAccess = "access"
|
||||
|
||||
// ResourceTypeAuditLog 表示审计日志
|
||||
ResourceTypeAuditLog = "audit_log"
|
||||
// ResourceTypeAccountCenter 表示账户中心
|
||||
ResourceTypeAccountCenter = "account_center"
|
||||
// ResourceTypeClusterManagement 表示集群管理
|
||||
ResourceTypeClusterManagement = "cluster_management"
|
||||
// ResourceTypeDevTool 表示开发工具
|
||||
ResourceTypeDevTool = "dev_tool"
|
||||
// ResourceTypeDataMigration 表示数据迁移
|
||||
ResourceTypeDataMigration = "data_migration"
|
||||
|
||||
// OperationTypeLogin 表示登录
|
||||
OperationTypeLogin = "login"
|
||||
// OperationTypeDeletion 表示删除
|
||||
OperationTypeDeletion = "deletion"
|
||||
// OperationTypeModification 表示修改
|
||||
OperationTypeModification = "modification"
|
||||
// OperationTypeNew 表示新建
|
||||
OperationTypeNew = "new"
|
||||
// OperationTypeAccess 表示访问
|
||||
OperationTypeAccess = "access"
|
||||
)
|
||||
|
||||
// CheckLogType 检查日志类型是否合法
|
||||
func CheckLogType(logType string) bool {
|
||||
switch logType {
|
||||
case LogTypeOperation, LogTypeAccess:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckResourceType 检查资源类型是否合法
|
||||
func CheckResourceType(resourceType string) bool {
|
||||
switch resourceType {
|
||||
case ResourceTypeAuditLog,
|
||||
ResourceTypeAccountCenter,
|
||||
ResourceTypeClusterManagement,
|
||||
ResourceTypeDevTool,
|
||||
ResourceTypeDataMigration:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckOperationType 检查操作类型是否合法
|
||||
func CheckOperationType(operationType string) bool {
|
||||
switch operationType {
|
||||
case OperationTypeLogin, OperationTypeDeletion,
|
||||
OperationTypeModification, OperationTypeNew, OperationTypeAccess:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AuditLog 表示审计日志
|
||||
type AuditLog struct {
|
||||
// 日志 ID,插入后返回
|
||||
ID string `json:"id,omitempty" elastic_meta:"_id" elastic_mapping:"id:{type:keyword}"`
|
||||
// 日志条目的创建时间
|
||||
Timestamp time.Time `json:"timestamp" elastic_mapping:"timestamp:{type:date}"`
|
||||
// 日志元数据
|
||||
Metadata AuditLogMetadata `json:"metadata" elastic_mapping:"metadata:{type:object}"`
|
||||
}
|
||||
|
||||
// AuditLogMetadata 表示审计日志元数据
|
||||
type AuditLogMetadata struct {
|
||||
// 操作者。比如 Zora
|
||||
Operator string `json:"operator"`
|
||||
// 日志类型。比如操作日志
|
||||
LogType string `json:"log_type" elastic_mapping:"log_type:{type:keyword}"`
|
||||
// 资源类型。比如审计日志
|
||||
ResourceType string `json:"resource_type" elastic_mapping:"resource_type:{type:keyword}"`
|
||||
// 事件定义。包括:
|
||||
// - event_name:事件名称。比如删除 ES 集群实例
|
||||
// - event_source_ip:事件源 IP。比如 175.0.24.182(中国 湖南省 长沙市 中国电信)
|
||||
// - team:团队。比如运维团队
|
||||
// - resource_name:资源名称。比如 es-7104
|
||||
// - operation:操作。比如删除
|
||||
// - event_record:事件记录。比如操作语句和查询参数
|
||||
Labels util.MapStr `json:"labels" elastic_mapping:"labels:{type:object}"`
|
||||
}
|
||||
|
||||
// Reset 将一些字段重置。比如在插入前清空 ID 等
|
||||
func (log *AuditLog) Reset() *AuditLog {
|
||||
log.ID = ""
|
||||
if log.Timestamp.IsZero() {
|
||||
log.Timestamp = time.Now()
|
||||
}
|
||||
return log
|
||||
}
|
||||
|
||||
// Validate 检查字段是否合法
|
||||
func (log *AuditLog) Validate() error {
|
||||
if log.Timestamp.IsZero() {
|
||||
return ErrCreatedTimeNotProvided
|
||||
}
|
||||
// 操作者不能为空
|
||||
if log.Metadata.Operator == "" {
|
||||
return ErrOperatorNotProvided
|
||||
}
|
||||
if !CheckLogType(log.Metadata.LogType) {
|
||||
return ErrLogTypeNotProvided
|
||||
}
|
||||
if !CheckResourceType(log.Metadata.ResourceType) {
|
||||
return ErrResourceTypeNotProvided
|
||||
}
|
||||
if log.Metadata.Labels == nil {
|
||||
return ErrEventNotProvided
|
||||
}
|
||||
// 事件名称可以为空
|
||||
// 检查事件源 IP 是否合法
|
||||
eventSourceIPAny, found := log.Metadata.Labels["event_source_ip"]
|
||||
if !found {
|
||||
return ErrEventSourceIPNotProvided
|
||||
}
|
||||
eventSourceIP, ok := eventSourceIPAny.(string)
|
||||
if !ok {
|
||||
return ErrEventSourceIPNotProvided
|
||||
}
|
||||
leftParenthesisIndex := strings.Index(eventSourceIP, "(")
|
||||
if leftParenthesisIndex < 0 {
|
||||
// 如果没有括号,那么将字段视为 IP 地址
|
||||
if net.ParseIP(eventSourceIP) == nil {
|
||||
return ErrEventSourceIPNotProvided
|
||||
}
|
||||
} else {
|
||||
ipPart := eventSourceIP[:leftParenthesisIndex]
|
||||
if net.ParseIP(ipPart) == nil {
|
||||
return ErrEventSourceIPNotProvided
|
||||
}
|
||||
ipPlacePart := eventSourceIP[leftParenthesisIndex:]
|
||||
if len(ipPlacePart) < 2 {
|
||||
// 允许 IP 归属地为空
|
||||
return ErrEventSourceIPNotProvided
|
||||
}
|
||||
}
|
||||
// 团队可以为空
|
||||
// 资源名称可以为空
|
||||
operationAny, found := log.Metadata.Labels["operation"]
|
||||
if !found {
|
||||
return ErrOperationNotProvided
|
||||
}
|
||||
operation, ok := operationAny.(string)
|
||||
if !ok || !CheckOperationType(operation) {
|
||||
return ErrOperationNotProvided
|
||||
}
|
||||
// 事件记录可以为空
|
||||
|
||||
// ID 可以为空
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuditLogBuilder 用于构造 AuditLog 实例
|
||||
type AuditLogBuilder struct {
|
||||
id string
|
||||
timestamp time.Time
|
||||
metaData AuditLogMetadata
|
||||
}
|
||||
|
||||
// NewAuditLogBuilder 构造空的 AuditLogBuilder 实例
|
||||
func NewAuditLogBuilder() *AuditLogBuilder {
|
||||
builder := new(AuditLogBuilder)
|
||||
builder.metaData.Labels = make(util.MapStr)
|
||||
return builder
|
||||
}
|
||||
|
||||
// NewAuditLogBuilderWithDefault 构造带默认值的 Builder
|
||||
func NewAuditLogBuilderWithDefault() *AuditLogBuilder {
|
||||
return NewAuditLogBuilder().
|
||||
WithOperator("").
|
||||
WithLogTypeAccess().
|
||||
WithResourceTypeAuditLog().
|
||||
WithEventName("").
|
||||
WithTimestampNow().
|
||||
WithEventSourceIP(net.IPv4zero.String()).
|
||||
WithTeam("").
|
||||
WithResourceName("").
|
||||
WithOperationTypeAccess().
|
||||
WithEventRecord("")
|
||||
}
|
||||
|
||||
// WithID 设置 ID
|
||||
func (builder *AuditLogBuilder) WithID(id string) *AuditLogBuilder {
|
||||
builder.id = id
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithOperator 设置操作者
|
||||
func (builder *AuditLogBuilder) WithOperator(operator string) *AuditLogBuilder {
|
||||
if operator == "" {
|
||||
operator = "unknown"
|
||||
}
|
||||
builder.metaData.Operator = operator
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithLogTypeOperation 将日志类型日志设置为操作日志
|
||||
func (builder *AuditLogBuilder) WithLogTypeOperation() *AuditLogBuilder {
|
||||
builder.metaData.LogType = LogTypeOperation
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithLogTypeAccess 将日志类型设置为访问日志
|
||||
func (builder *AuditLogBuilder) WithLogTypeAccess() *AuditLogBuilder {
|
||||
builder.metaData.LogType = LogTypeAccess
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResourceTypeAuditLog 将资源类型设置为审计日志
|
||||
func (builder *AuditLogBuilder) WithResourceTypeAuditLog() *AuditLogBuilder {
|
||||
builder.metaData.ResourceType = ResourceTypeAuditLog
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResourceTypeAccountCenter 将资源类型设置为账户中心
|
||||
func (builder *AuditLogBuilder) WithResourceTypeAccountCenter() *AuditLogBuilder {
|
||||
builder.metaData.ResourceType = ResourceTypeAccountCenter
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResourceTypeClusterManagement 将资源类型设置为集群管理
|
||||
func (builder *AuditLogBuilder) WithResourceTypeClusterManagement() *AuditLogBuilder {
|
||||
builder.metaData.ResourceType = ResourceTypeClusterManagement
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResourceTypeDevTool 将资源类型设置为开发工具
|
||||
func (builder *AuditLogBuilder) WithResourceTypeDevTool() *AuditLogBuilder {
|
||||
builder.metaData.ResourceType = ResourceTypeDevTool
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResourceTypeDataMigration 将资源类型设置为数据迁移
|
||||
func (builder *AuditLogBuilder) WithResourceTypeDataMigration() *AuditLogBuilder {
|
||||
builder.metaData.ResourceType = ResourceTypeDataMigration
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithEventName 设置事件名称
|
||||
func (builder *AuditLogBuilder) WithEventName(eventName string) *AuditLogBuilder {
|
||||
builder.metaData.Labels["event_name"] = eventName
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithTimestamp 设置时间戳
|
||||
func (builder *AuditLogBuilder) WithTimestamp(eventTime time.Time) *AuditLogBuilder {
|
||||
builder.timestamp = eventTime
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithTimestampNow 将时间戳设置为当前时间
|
||||
func (builder *AuditLogBuilder) WithTimestampNow() *AuditLogBuilder {
|
||||
return builder.WithTimestamp(time.Now())
|
||||
}
|
||||
|
||||
// WithEventSourceIPAndPlace 设置事件源 IP 和 IP 归属地
|
||||
func (builder *AuditLogBuilder) WithEventSourceIPAndPlace(eventSourceIP, ipPlace string) *AuditLogBuilder {
|
||||
if net.ParseIP(eventSourceIP) == nil {
|
||||
// 对于无效 IP,保存为 0.0.0.0
|
||||
eventSourceIP = net.IPv4zero.String()
|
||||
}
|
||||
// 如果 IP 归属地为空,那么省略括号中的部分
|
||||
s := eventSourceIP
|
||||
if ipPlace != "" {
|
||||
s = fmt.Sprintf("%s(%s)", eventSourceIP, ipPlace)
|
||||
}
|
||||
builder.metaData.Labels["event_source_ip"] = s
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithEventSourceIP 设置事件源 IP,将根据 IP 地址获取其归属地
|
||||
func (builder *AuditLogBuilder) WithEventSourceIP(eventSourceIP string) *AuditLogBuilder {
|
||||
ipPlace := ""
|
||||
// TODO:根据 IP 地址获取归属地
|
||||
return builder.WithEventSourceIPAndPlace(eventSourceIP, ipPlace)
|
||||
}
|
||||
|
||||
// WithTeam 设置团队
|
||||
func (builder *AuditLogBuilder) WithTeam(team string) *AuditLogBuilder {
|
||||
builder.metaData.Labels["team"] = team
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithResourceName 设置资源名称
|
||||
func (builder *AuditLogBuilder) WithResourceName(resourceName string) *AuditLogBuilder {
|
||||
builder.metaData.Labels["resource_name"] = resourceName
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithOperationTypeLogin 将操作类型设置为登录
|
||||
func (builder *AuditLogBuilder) WithOperationTypeLogin() *AuditLogBuilder {
|
||||
builder.metaData.Labels["operation"] = OperationTypeLogin
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithOperationTypeDeletion 将操作类型设置为删除
|
||||
func (builder *AuditLogBuilder) WithOperationTypeDeletion() *AuditLogBuilder {
|
||||
builder.metaData.Labels["operation"] = OperationTypeDeletion
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithOperationTypeModification 将操作类型设置为修改
|
||||
func (builder *AuditLogBuilder) WithOperationTypeModification() *AuditLogBuilder {
|
||||
builder.metaData.Labels["operation"] = OperationTypeModification
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithOperationTypeNew 将操作类型设置为新建
|
||||
func (builder *AuditLogBuilder) WithOperationTypeNew() *AuditLogBuilder {
|
||||
builder.metaData.Labels["operation"] = OperationTypeNew
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithOperationTypeAccess 将操作类型设置为访问
|
||||
func (builder *AuditLogBuilder) WithOperationTypeAccess() *AuditLogBuilder {
|
||||
builder.metaData.Labels["operation"] = OperationTypeAccess
|
||||
return builder
|
||||
}
|
||||
|
||||
// WithEventRecord 设置事件记录,比如操作语句或查询参数
|
||||
func (builder *AuditLogBuilder) WithEventRecord(eventRecord string) *AuditLogBuilder {
|
||||
builder.metaData.Labels["event_record"] = eventRecord
|
||||
return builder
|
||||
}
|
||||
|
||||
func (builder *AuditLogBuilder) Build() (a *AuditLog, err error) {
|
||||
// 构造对象
|
||||
a = new(AuditLog)
|
||||
a.ID = builder.id
|
||||
a.Timestamp = builder.timestamp
|
||||
a.Metadata = builder.metaData
|
||||
// 校验构造的对象
|
||||
err = a.Validate()
|
||||
return
|
||||
}
|
|
@ -3,6 +3,10 @@ package index_management
|
|||
import (
|
||||
"fmt"
|
||||
log "github.com/cihub/seelog"
|
||||
"infini.sh/console/common"
|
||||
"infini.sh/console/model"
|
||||
"infini.sh/console/service"
|
||||
"infini.sh/framework/core/api/rbac"
|
||||
httprouter "infini.sh/framework/core/api/router"
|
||||
"infini.sh/framework/core/elastic"
|
||||
"infini.sh/framework/core/event"
|
||||
|
@ -17,9 +21,9 @@ import (
|
|||
|
||||
func (handler APIHandler) ElasticsearchOverviewAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||
var (
|
||||
totalNode int
|
||||
totalNode int
|
||||
totalStoreSize int
|
||||
clusterIDs []interface{}
|
||||
clusterIDs []interface{}
|
||||
)
|
||||
//elastic.WalkConfigs(func(key, value interface{})bool{
|
||||
// if handler.Config.Elasticsearch == key {
|
||||
|
@ -33,13 +37,13 @@ func (handler APIHandler) ElasticsearchOverviewAction(w http.ResponseWriter, req
|
|||
"size": 100,
|
||||
}
|
||||
clusterFilter, hasAllPrivilege := handler.GetClusterFilter(req, "_id")
|
||||
if !hasAllPrivilege && clusterFilter == nil{
|
||||
if !hasAllPrivilege && clusterFilter == nil {
|
||||
handler.WriteJSON(w, util.MapStr{
|
||||
"nodes_count": 0,
|
||||
"clusters_count":0,
|
||||
"nodes_count": 0,
|
||||
"clusters_count": 0,
|
||||
"total_used_store_in_bytes": 0,
|
||||
"hosts_count": 0,
|
||||
"indices_count": 0,
|
||||
"hosts_count": 0,
|
||||
"indices_count": 0,
|
||||
}, http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
@ -47,6 +51,16 @@ func (handler APIHandler) ElasticsearchOverviewAction(w http.ResponseWriter, req
|
|||
queryDsl["query"] = clusterFilter
|
||||
}
|
||||
|
||||
user, auditLogErr := rbac.FromUserContext(req.Context())
|
||||
if auditLogErr == nil && handler.GetHeader(req, "Referer", "") != "" {
|
||||
auditLog, _ := model.NewAuditLogBuilderWithDefault().WithOperator(user.Username).
|
||||
WithLogTypeAccess().WithResourceTypeClusterManagement().
|
||||
WithEventName("get elasticsearch overview").WithEventSourceIP(common.GetClientIP(req)).
|
||||
WithResourceName("elasticsearch").WithOperationTypeAccess().
|
||||
WithEventRecord(util.MustToJSON(queryDsl)).Build()
|
||||
_ = service.LogAuditLog(auditLog)
|
||||
}
|
||||
|
||||
searchRes, err := esClient.SearchWithRawQueryDSL(orm.GetIndexName(elastic.ElasticsearchConfig{}), util.MustToJSONBytes(queryDsl))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
@ -86,39 +100,39 @@ func (handler APIHandler) ElasticsearchOverviewAction(w http.ResponseWriter, req
|
|||
}
|
||||
|
||||
hostCount, err := handler.getMetricCount(orm.GetIndexName(host.HostInfo{}), "ip", nil)
|
||||
if err != nil{
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if v, ok := hostCount.(float64); (ok && v == 0) || hostCount == nil {
|
||||
hostCount, err = handler.getMetricCount(orm.GetIndexName(elastic.NodeConfig{}), "metadata.host", clusterIDs)
|
||||
if err != nil{
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
nodeCount, err := handler.getMetricCount(orm.GetIndexName(elastic.NodeConfig{}), "id", clusterIDs)
|
||||
if err != nil{
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if v, ok := nodeCount.(float64); ok {
|
||||
totalNode = int(v)
|
||||
}
|
||||
indicesCount, err := handler.getIndexCount(req)
|
||||
if err != nil{
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
||||
resBody := util.MapStr{
|
||||
"nodes_count": totalNode,
|
||||
"clusters_count": len(clusterIDs),
|
||||
"nodes_count": totalNode,
|
||||
"clusters_count": len(clusterIDs),
|
||||
"total_used_store_in_bytes": totalStoreSize,
|
||||
"hosts_count": hostCount,
|
||||
"indices_count": indicesCount,
|
||||
"hosts_count": hostCount,
|
||||
"indices_count": indicesCount,
|
||||
}
|
||||
handler.WriteJSON(w, resBody, http.StatusOK)
|
||||
}
|
||||
|
||||
func (handler APIHandler) getLatestClusterMonitorData(clusterIDs []interface{}) (*elastic.SearchResponse, error){
|
||||
func (handler APIHandler) getLatestClusterMonitorData(clusterIDs []interface{}) (*elastic.SearchResponse, error) {
|
||||
client := elastic.GetClient(global.MustLookupString(elastic.GlobalSystemElasticsearchID))
|
||||
queryDSLTpl := `{
|
||||
"size": %d,
|
||||
|
@ -243,18 +257,18 @@ func (handler APIHandler) getIndexCount(req *http.Request) (int64, error) {
|
|||
return orm.Count(elastic.IndexConfig{}, body)
|
||||
}
|
||||
|
||||
func (handler APIHandler) getMetricCount(indexName, field string, clusterIDs []interface{}) (interface{}, error){
|
||||
func (handler APIHandler) getMetricCount(indexName, field string, clusterIDs []interface{}) (interface{}, error) {
|
||||
client := elastic.GetClient(global.MustLookupString(elastic.GlobalSystemElasticsearchID))
|
||||
queryDSL := util.MapStr{
|
||||
"size": 0,
|
||||
"aggs": util.MapStr{
|
||||
"field_count": util.MapStr{
|
||||
"cardinality": util.MapStr{
|
||||
"field": field,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
"size": 0,
|
||||
"aggs": util.MapStr{
|
||||
"field_count": util.MapStr{
|
||||
"cardinality": util.MapStr{
|
||||
"field": field,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if len(clusterIDs) > 0 {
|
||||
queryDSL["query"] = util.MapStr{
|
||||
"terms": util.MapStr{
|
||||
|
@ -270,7 +284,7 @@ func (handler APIHandler) getMetricCount(indexName, field string, clusterIDs []i
|
|||
return searchRes.Aggregations["field_count"].Value, nil
|
||||
}
|
||||
|
||||
func (handler APIHandler) getLastActiveHostCount() (int, error){
|
||||
func (handler APIHandler) getLastActiveHostCount() (int, error) {
|
||||
client := elastic.GetClient(global.MustLookupString(elastic.GlobalSystemElasticsearchID))
|
||||
queryDSL := `{
|
||||
"size": 0,
|
||||
|
@ -321,12 +335,12 @@ func (handler APIHandler) getLastActiveHostCount() (int, error){
|
|||
return len(searchRes.Aggregations["week_active_host"].Buckets), nil
|
||||
}
|
||||
|
||||
func (handler APIHandler) ElasticsearchStatusSummaryAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params){
|
||||
func (handler APIHandler) ElasticsearchStatusSummaryAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||
clusterIDs, hasAllPrivilege := handler.GetAllowedClusters(req)
|
||||
if !hasAllPrivilege && len(clusterIDs) == 0{
|
||||
if !hasAllPrivilege && len(clusterIDs) == 0 {
|
||||
handler.WriteJSON(w, util.MapStr{
|
||||
"cluster": util.MapStr{},
|
||||
"node": util.MapStr{},
|
||||
"node": util.MapStr{},
|
||||
"host": util.MapStr{
|
||||
"online": 0,
|
||||
},
|
||||
|
@ -375,14 +389,14 @@ func (handler APIHandler) ElasticsearchStatusSummaryAction(w http.ResponseWriter
|
|||
}
|
||||
handler.WriteJSON(w, util.MapStr{
|
||||
"cluster": clusterGrp,
|
||||
"node": nodeGrp,
|
||||
"node": nodeGrp,
|
||||
"host": util.MapStr{
|
||||
"online": hostCount,
|
||||
},
|
||||
}, http.StatusOK)
|
||||
}
|
||||
|
||||
func (handler APIHandler) getGroupMetric(indexName, field string, filter interface{}) (interface{}, error){
|
||||
func (handler APIHandler) getGroupMetric(indexName, field string, filter interface{}) (interface{}, error) {
|
||||
client := elastic.GetClient(global.MustLookupString(elastic.GlobalSystemElasticsearchID))
|
||||
queryDSL := util.MapStr{
|
||||
"size": 0,
|
||||
|
@ -561,4 +575,4 @@ func (h *APIHandler) ClusterOverTreeMap(w http.ResponseWriter, req *http.Request
|
|||
}
|
||||
|
||||
h.Write(w, util.MustToJSONBytes(result))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,10 @@ package index_management
|
|||
|
||||
import (
|
||||
log "github.com/cihub/seelog"
|
||||
"infini.sh/console/common"
|
||||
"infini.sh/console/model"
|
||||
"infini.sh/console/service"
|
||||
"infini.sh/framework/core/api/rbac"
|
||||
httprouter "infini.sh/framework/core/api/router"
|
||||
"infini.sh/framework/core/elastic"
|
||||
"infini.sh/framework/core/radix"
|
||||
|
@ -39,11 +43,19 @@ func (handler APIHandler) HandleGetMappingsAction(w http.ResponseWriter, req *ht
|
|||
|
||||
func (handler APIHandler) HandleCatIndicesAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||
targetClusterID := ps.ByName("id")
|
||||
user, auditLogErr := rbac.FromUserContext(req.Context())
|
||||
if auditLogErr == nil && handler.GetHeader(req, "Referer", "") != "" {
|
||||
auditLog, _ := model.NewAuditLogBuilderWithDefault().WithOperator(user.Username).
|
||||
WithLogTypeAccess().WithResourceTypeClusterManagement().
|
||||
WithEventName("get indices").WithEventSourceIP(common.GetClientIP(req)).
|
||||
WithResourceName(targetClusterID).WithOperationTypeAccess().WithEventRecord("").Build()
|
||||
_ = service.LogAuditLog(auditLog)
|
||||
}
|
||||
client := elastic.GetClient(targetClusterID)
|
||||
//filter indices
|
||||
allowedIndices, hasAllPrivilege := handler.GetAllowedIndices(req, targetClusterID)
|
||||
if !hasAllPrivilege && len(allowedIndices) == 0 {
|
||||
handler.WriteJSON(w, []interface{}{} , http.StatusOK)
|
||||
handler.WriteJSON(w, []interface{}{}, http.StatusOK)
|
||||
return
|
||||
}
|
||||
catIndices, err := client.GetIndices("")
|
||||
|
@ -58,7 +70,7 @@ func (handler APIHandler) HandleCatIndicesAction(w http.ResponseWriter, req *htt
|
|||
filterIndices := map[string]elastic.IndexInfo{}
|
||||
pattern := radix.Compile(allowedIndices...)
|
||||
for indexName, indexInfo := range *catIndices {
|
||||
if pattern.Match(indexName){
|
||||
if pattern.Match(indexName) {
|
||||
filterIndices[indexName] = indexInfo
|
||||
}
|
||||
}
|
||||
|
@ -123,10 +135,18 @@ func (handler APIHandler) HandleDeleteIndexAction(w http.ResponseWriter, req *ht
|
|||
}
|
||||
|
||||
func (handler APIHandler) HandleCreateIndexAction(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||
|
||||
|
||||
targetClusterID := ps.ByName("id")
|
||||
client := elastic.GetClient(targetClusterID)
|
||||
indexName := ps.ByName("index")
|
||||
claims, auditLogErr := rbac.ValidateLogin(req.Header.Get("Authorization"))
|
||||
if auditLogErr == nil && handler.GetHeader(req, "Referer", "") != "" {
|
||||
auditLog, _ := model.NewAuditLogBuilderWithDefault().WithOperator(claims.Username).
|
||||
WithLogTypeOperation().WithResourceTypeClusterManagement().
|
||||
WithEventName("create index").WithEventSourceIP(common.GetClientIP(req)).
|
||||
WithResourceName(targetClusterID).WithOperationTypeNew().WithEventRecord(indexName).Build()
|
||||
auditLogErr = service.LogAuditLog(auditLog)
|
||||
}
|
||||
resBody := newResponseBody()
|
||||
config := map[string]interface{}{}
|
||||
err := handler.DecodeJSON(req, &config)
|
||||
|
@ -159,4 +179,4 @@ func (handler APIHandler) HandleGetIndexAction(w http.ResponseWriter, req *http.
|
|||
}
|
||||
handler.WriteJSON(w, indexRes, http.StatusOK)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"infini.sh/console/plugin/api/data"
|
||||
"infini.sh/console/plugin/api/email"
|
||||
"infini.sh/console/plugin/api/license"
|
||||
"infini.sh/console/plugin/api/platform"
|
||||
"path"
|
||||
|
||||
"infini.sh/console/config"
|
||||
"infini.sh/console/plugin/api/alerting"
|
||||
"infini.sh/console/plugin/api/data"
|
||||
"infini.sh/console/plugin/api/email"
|
||||
"infini.sh/console/plugin/api/index_management"
|
||||
"infini.sh/console/plugin/api/insight"
|
||||
"infini.sh/console/plugin/api/layout"
|
||||
"infini.sh/console/plugin/api/license"
|
||||
"infini.sh/console/plugin/api/notification"
|
||||
"infini.sh/console/plugin/api/platform"
|
||||
"infini.sh/framework/core/api"
|
||||
"infini.sh/framework/core/api/rbac/enum"
|
||||
"path"
|
||||
)
|
||||
|
||||
func Init(cfg *config.AppConfig) {
|
||||
|
|
|
@ -7,6 +7,9 @@ package platform
|
|||
import (
|
||||
"fmt"
|
||||
log "github.com/cihub/seelog"
|
||||
"infini.sh/console/common"
|
||||
"infini.sh/console/model"
|
||||
"infini.sh/console/service"
|
||||
"infini.sh/framework/core/api"
|
||||
"infini.sh/framework/core/api/rbac"
|
||||
httprouter "infini.sh/framework/core/api/router"
|
||||
|
@ -33,13 +36,13 @@ func (h *PlatformAPI) searchCollection(w http.ResponseWriter, req *http.Request,
|
|||
collMetas := GetCollectionMetas()
|
||||
var (
|
||||
meta CollectionMeta
|
||||
ok bool
|
||||
ok bool
|
||||
)
|
||||
if meta, ok = collMetas[collName]; !ok {
|
||||
h.WriteError(w, fmt.Sprintf("metadata of collection [%s] not found", collName), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if api.IsAuthEnable(){
|
||||
if api.IsAuthEnable() {
|
||||
claims, err := rbac.ValidateLogin(req.Header.Get("Authorization"))
|
||||
if err != nil {
|
||||
h.WriteError(w, err.Error(), http.StatusUnauthorized)
|
||||
|
@ -61,8 +64,7 @@ func (h *PlatformAPI) searchCollection(w http.ResponseWriter, req *http.Request,
|
|||
if meta.GetSearchRequestBodyFilter != nil {
|
||||
filter, hasAllPrivilege := meta.GetSearchRequestBodyFilter(h, req)
|
||||
if !hasAllPrivilege && filter == nil {
|
||||
h.WriteJSON(w, elastic.SearchResponse{
|
||||
}, http.StatusOK)
|
||||
h.WriteJSON(w, elastic.SearchResponse{}, http.StatusOK)
|
||||
return
|
||||
}
|
||||
if !hasAllPrivilege {
|
||||
|
@ -84,10 +86,10 @@ func (h *PlatformAPI) searchCollection(w http.ResponseWriter, req *http.Request,
|
|||
h.WriteError(w, string(searchRes.RawResult.Body), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.WriteJSON(w, searchRes,http.StatusOK)
|
||||
h.WriteJSON(w, searchRes, http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *PlatformAPI) rewriteQueryWithFilter(queryDsl []byte, filter util.MapStr) ([]byte, error){
|
||||
func (h *PlatformAPI) rewriteQueryWithFilter(queryDsl []byte, filter util.MapStr) ([]byte, error) {
|
||||
|
||||
mapObj := util.MapStr{}
|
||||
err := util.FromJSONBytes(queryDsl, &mapObj)
|
||||
|
@ -122,13 +124,23 @@ func (h *PlatformAPI) rewriteQueryWithFilter(queryDsl []byte, filter util.MapStr
|
|||
return queryDsl, nil
|
||||
}
|
||||
|
||||
//getCollectionMeta returns metadata of target collection, includes backend index name
|
||||
// getCollectionMeta returns metadata of target collection, includes backend index name
|
||||
func (h *PlatformAPI) getCollectionMeta(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
|
||||
collName := ps.MustGetParameter("collection_name")
|
||||
if collName == "activity" {
|
||||
user, auditLogErr := rbac.FromUserContext(req.Context())
|
||||
if auditLogErr == nil && h.GetHeader(req, "Referer", "") != "" {
|
||||
auditLog, _ := model.NewAuditLogBuilderWithDefault().WithOperator(user.Username).
|
||||
WithLogTypeAccess().WithResourceTypeAccountCenter().
|
||||
WithEventName("get activity meta").WithEventSourceIP(common.GetClientIP(req)).
|
||||
WithResourceName("activity").WithOperationTypeAccess().WithEventRecord("").Build()
|
||||
_ = service.LogAuditLog(auditLog)
|
||||
}
|
||||
}
|
||||
collMetas := GetCollectionMetas()
|
||||
var (
|
||||
meta CollectionMeta
|
||||
ok bool
|
||||
ok bool
|
||||
)
|
||||
if meta, ok = collMetas[collName]; !ok {
|
||||
h.WriteError(w, fmt.Sprintf("metadata of collection [%s] not found", collName), http.StatusInternalServerError)
|
||||
|
@ -141,4 +153,4 @@ func (h *PlatformAPI) getCollectionMeta(w http.ResponseWriter, req *http.Request
|
|||
"index_name": indexName,
|
||||
},
|
||||
}, http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
package platform
|
||||
|
||||
import (
|
||||
consoleModel "infini.sh/console/model"
|
||||
"infini.sh/console/model/alerting"
|
||||
"infini.sh/framework/core/api/rbac/enum"
|
||||
"infini.sh/framework/core/elastic"
|
||||
|
@ -18,10 +19,10 @@ import (
|
|||
|
||||
var (
|
||||
collectionMetas map[string]CollectionMeta
|
||||
metasInitOnce sync.Once
|
||||
metasInitOnce sync.Once
|
||||
)
|
||||
|
||||
func GetCollectionMetas() map[string]CollectionMeta{
|
||||
func GetCollectionMetas() map[string]CollectionMeta {
|
||||
metasInitOnce.Do(func() {
|
||||
collectionMetas = map[string]CollectionMeta{
|
||||
"gateway": {
|
||||
|
@ -108,10 +109,10 @@ func GetCollectionMetas() map[string]CollectionMeta{
|
|||
for clusterID, indices := range indexPrivilege {
|
||||
var (
|
||||
wildcardIndices []string
|
||||
normalIndices []string
|
||||
normalIndices []string
|
||||
)
|
||||
for _, index := range indices {
|
||||
if strings.Contains(index,"*") {
|
||||
if strings.Contains(index, "*") {
|
||||
wildcardIndices = append(wildcardIndices, index)
|
||||
continue
|
||||
}
|
||||
|
@ -121,8 +122,8 @@ func GetCollectionMetas() map[string]CollectionMeta{
|
|||
if len(wildcardIndices) > 0 {
|
||||
subShould = append(subShould, util.MapStr{
|
||||
"query_string": util.MapStr{
|
||||
"query": strings.Join(wildcardIndices, " "),
|
||||
"fields": []string{"metadata.labels.index_name"},
|
||||
"query": strings.Join(wildcardIndices, " "),
|
||||
"fields": []string{"metadata.labels.index_name"},
|
||||
"default_operator": "OR",
|
||||
},
|
||||
})
|
||||
|
@ -147,7 +148,7 @@ func GetCollectionMetas() map[string]CollectionMeta{
|
|||
{
|
||||
"bool": util.MapStr{
|
||||
"minimum_should_match": 1,
|
||||
"should": subShould,
|
||||
"should": subShould,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -157,7 +158,7 @@ func GetCollectionMetas() map[string]CollectionMeta{
|
|||
indexFilter := util.MapStr{
|
||||
"bool": util.MapStr{
|
||||
"minimum_should_match": 1,
|
||||
"should": indexShould,
|
||||
"should": indexShould,
|
||||
},
|
||||
}
|
||||
filter = append(filter, indexFilter)
|
||||
|
@ -169,6 +170,15 @@ func GetCollectionMetas() map[string]CollectionMeta{
|
|||
}, hasAllPrivilege
|
||||
},
|
||||
},
|
||||
"audit_log": {
|
||||
Name: "audit_log",
|
||||
RequirePermission: map[string][]string{
|
||||
"read": {
|
||||
enum.PermissionAuditLogRead,
|
||||
},
|
||||
},
|
||||
MatchObject: &consoleModel.AuditLog{},
|
||||
},
|
||||
"alerting_rule": {
|
||||
Name: "alerting_rule",
|
||||
RequirePermission: map[string][]string{
|
||||
|
@ -182,7 +192,8 @@ func GetCollectionMetas() map[string]CollectionMeta{
|
|||
})
|
||||
return collectionMetas
|
||||
}
|
||||
//CollectionMeta includes information about how to visit backend index
|
||||
|
||||
// CollectionMeta includes information about how to visit backend index
|
||||
type CollectionMeta struct {
|
||||
//collection name
|
||||
Name string `json:"name"`
|
||||
|
@ -194,4 +205,4 @@ type CollectionMeta struct {
|
|||
GetSearchRequestBodyFilter SearchRequestBodyFilter
|
||||
}
|
||||
|
||||
type SearchRequestBodyFilter func(api *PlatformAPI, req *http.Request) (util.MapStr, bool)
|
||||
type SearchRequestBodyFilter func(api *PlatformAPI, req *http.Request) (util.MapStr, bool)
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/* Copyright © INFINI Ltd. All rights reserved.
|
||||
* Web: https://infinilabs.com
|
||||
* Email: hello#infini.ltd */
|
||||
|
||||
package audit_log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"infini.sh/console/common"
|
||||
"infini.sh/console/model"
|
||||
"infini.sh/console/service"
|
||||
"infini.sh/framework/core/api"
|
||||
"infini.sh/framework/core/api/rbac"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var overviewRegexp = regexp.MustCompile(`/elasticsearch/([^/]+)/(cluster_metrics|metrics|nodes/realtime|indices/realtime)`)
|
||||
|
||||
var _ api.Interceptor = (*MonitoringInterceptor)(nil)
|
||||
|
||||
type MonitoringInterceptor struct{}
|
||||
|
||||
func (m *MonitoringInterceptor) Match(request *http.Request) bool {
|
||||
return overviewRegexp.MatchString(request.URL.Path)
|
||||
}
|
||||
|
||||
func (m *MonitoringInterceptor) PreHandle(c context.Context, _ http.ResponseWriter,
|
||||
request *http.Request) (context.Context, error) {
|
||||
handler := &api.Handler{}
|
||||
targetClusterID := ""
|
||||
eventName := ""
|
||||
matches := overviewRegexp.FindStringSubmatch(request.URL.Path)
|
||||
if len(matches) > 1 {
|
||||
targetClusterID = matches[1]
|
||||
eventName = strings.Replace(matches[2], "/", " ", -1)
|
||||
}
|
||||
claims, auditLogErr := rbac.ValidateLogin(request.Header.Get("Authorization"))
|
||||
if auditLogErr == nil && handler.GetHeader(request, "Referer", "") != "" {
|
||||
auditLog, _ := model.NewAuditLogBuilderWithDefault().WithOperator(claims.Username).
|
||||
WithLogTypeAccess().WithResourceTypeClusterManagement().
|
||||
WithEventName("monitoring " + eventName).WithEventSourceIP(common.GetClientIP(request)).
|
||||
WithResourceName(targetClusterID).WithOperationTypeAccess().WithEventRecord(request.URL.RawQuery).Build()
|
||||
_ = service.LogAuditLog(auditLog)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (m *MonitoringInterceptor) PostHandle(_ context.Context, _ http.ResponseWriter, _ *http.Request) {
|
||||
}
|
||||
|
||||
func (m *MonitoringInterceptor) Name() string {
|
||||
return "monitoring_interceptor"
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/* Copyright © INFINI Ltd. All rights reserved.
|
||||
* Web: https://infinilabs.com
|
||||
* Email: hello#infini.ltd */
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"infini.sh/console/model"
|
||||
"infini.sh/framework/core/queue"
|
||||
"infini.sh/framework/core/util"
|
||||
)
|
||||
|
||||
const AuditLogQueueName = "logging-audit-log-queue"
|
||||
|
||||
type AuditLogAction struct {
|
||||
log *model.AuditLog
|
||||
}
|
||||
|
||||
func NewAuditLogAction(log *model.AuditLog) *AuditLogAction {
|
||||
return &AuditLogAction{
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (action AuditLogAction) Execute() ([]byte, error) {
|
||||
queueCfg := queue.GetOrInitConfig(AuditLogQueueName)
|
||||
msg := util.MustToJSONBytes(action.log)
|
||||
err := queue.Push(queueCfg, msg)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// LogAuditLog 记录审计日志
|
||||
func LogAuditLog(log *model.AuditLog) (err error) {
|
||||
if err = log.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = NewAuditLogAction(log).Execute()
|
||||
return
|
||||
}
|
Loading…
Reference in New Issue