Skip to content

Commit d6222b1

Browse files
authored
Merge pull request #195 from AikidoSec/monitor-mode
Added monitoring-mode support for ips and user agents
2 parents adea8d6 + 8a75dcd commit d6222b1

File tree

32 files changed

+542
-83
lines changed

32 files changed

+542
-83
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,4 +499,4 @@ jobs:
499499
name: test-results-aikido-${{ env.AIKIDO_VERSION }}-${{ matrix.os }}-php-${{ matrix.php_version }}
500500
if-no-files-found: ignore
501501
path: |
502-
${{ github.workspace }}/tests/cli/**/*.diff
502+
${{ github.workspace }}/tests/cli/**/*.diff

lib/agent/aikido_types/events.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,23 @@ type MonitoredSinkStats struct {
5252
CompressedTimings []CompressedTiming `json:"compressedTimings"`
5353
}
5454

55+
type MonitoredListsBreakdown struct {
56+
Breakdown map[string]int `json:"breakdown"`
57+
}
58+
5559
type Requests struct {
5660
Total int `json:"total"`
5761
Aborted int `json:"aborted"`
5862
AttacksDetected AttacksDetected `json:"attacksDetected"`
5963
}
6064

6165
type Stats struct {
62-
Sinks map[string]MonitoredSinkStats `json:"sinks"`
63-
StartedAt int64 `json:"startedAt"`
64-
EndedAt int64 `json:"endedAt"`
65-
Requests Requests `json:"requests"`
66+
Sinks map[string]MonitoredSinkStats `json:"sinks"`
67+
StartedAt int64 `json:"startedAt"`
68+
EndedAt int64 `json:"endedAt"`
69+
Requests Requests `json:"requests"`
70+
UserAgents MonitoredListsBreakdown `json:"userAgents"`
71+
IpAddresses MonitoredListsBreakdown `json:"ipAddresses"`
6672
}
6773

6874
type AgentInfo struct {

lib/agent/aikido_types/init_data.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ type CloudConfigData struct {
6060
Block *bool `json:"block,omitempty"`
6161
BlockedIpsList map[string]IpBlocklist
6262
BlockedUserAgents string
63+
MonitoredIpsList map[string]IpBlocklist
64+
MonitoredUserAgents string
65+
UserAgentDetails map[string]string
6366
}
6467

6568
type BlockedIpsData struct {
@@ -68,11 +71,19 @@ type BlockedIpsData struct {
6871
Ips []string `json:"ips"`
6972
}
7073

74+
type UserAgentDetails struct {
75+
Key string `json:"key"`
76+
Pattern string `json:"pattern"`
77+
}
78+
7179
type ListsConfigData struct {
72-
Success bool `json:"success"`
73-
ServiceId int `json:"serviceId"`
74-
BlockedIpAddresses []BlockedIpsData `json:"blockedIPAddresses"`
75-
BlockedUserAgents string `json:"blockedUserAgents"`
80+
Success bool `json:"success"`
81+
ServiceId int `json:"serviceId"`
82+
BlockedIpAddresses []BlockedIpsData `json:"blockedIPAddresses"`
83+
BlockedUserAgents string `json:"blockedUserAgents"`
84+
MonitoredIpAddresses []BlockedIpsData `json:"monitoredIpAddresses"`
85+
MonitoredUserAgents string `json:"monitoredUserAgents"`
86+
UserAgentDetails []UserAgentDetails `json:"userAgentDetails"`
7687
}
7788

7889
type CloudConfigUpdatedAt struct {

lib/agent/aikido_types/stats.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ type StatsDataType struct {
2020
AttacksBlocked int
2121

2222
MonitoredSinkTimings map[string]MonitoredSinkTimings
23+
24+
UserAgentsMatches map[string]int
25+
IpAddressesMatches map[string]int
2326
}
2427

2528
type RateLimitingConfig struct {

lib/agent/cloud/common.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ func ApplyCloudConfig() {
101101
UpdateRateLimitingConfig()
102102
}
103103

104+
func UpdateIpsLists(BlockedIps []BlockedIpsData) map[string]IpBlocklist {
105+
m := make(map[string]IpBlocklist)
106+
for _, blockedIpsGroup := range BlockedIps {
107+
m[blockedIpsGroup.Source] = IpBlocklist{Description: blockedIpsGroup.Description, Ips: blockedIpsGroup.Ips}
108+
}
109+
return m
110+
}
111+
104112
func UpdateListsConfig() bool {
105113
response, err := SendCloudRequest(globals.EnvironmentConfig.Endpoint, globals.ListsAPI, globals.ListsAPIMethod, nil)
106114
if err != nil {
@@ -111,15 +119,21 @@ func UpdateListsConfig() bool {
111119
tempListsConfig := ListsConfigData{}
112120
err = json.Unmarshal(response, &tempListsConfig)
113121
if err != nil {
114-
log.Warnf("Failed to unmarshal lists config!")
122+
log.Warnf("Failed to unmarshal lists config: %v", err)
115123
return false
116124
}
117125

118-
CloudConfig.BlockedIpsList = make(map[string]IpBlocklist)
119-
for _, blockedIpsGroup := range tempListsConfig.BlockedIpAddresses {
120-
CloudConfig.BlockedIpsList[blockedIpsGroup.Source] = IpBlocklist{Description: blockedIpsGroup.Description, Ips: blockedIpsGroup.Ips}
121-
}
126+
CloudConfig.BlockedIpsList = UpdateIpsLists(tempListsConfig.BlockedIpAddresses)
127+
CloudConfig.MonitoredIpsList = UpdateIpsLists(tempListsConfig.MonitoredIpAddresses)
128+
122129
CloudConfig.BlockedUserAgents = tempListsConfig.BlockedUserAgents
130+
CloudConfig.MonitoredUserAgents = tempListsConfig.MonitoredUserAgents
131+
132+
CloudConfig.UserAgentDetails = make(map[string]string)
133+
for _, userAgentDetail := range tempListsConfig.UserAgentDetails {
134+
CloudConfig.UserAgentDetails[userAgentDetail.Key] = userAgentDetail.Pattern
135+
}
136+
123137
return true
124138
}
125139

lib/agent/cloud/event_heartbeat.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,22 @@ func GetMonitoredSinkStatsAndClear() map[string]MonitoredSinkStats {
8484
return monitoredSinkStats
8585
}
8686

87+
func GetIpsBreakdownAndClear() MonitoredListsBreakdown {
88+
m := MonitoredListsBreakdown{
89+
Breakdown: globals.StatsData.IpAddressesMatches,
90+
}
91+
globals.StatsData.IpAddressesMatches = make(map[string]int)
92+
return m
93+
}
94+
95+
func GetUserAgentsBreakdownAndClear() MonitoredListsBreakdown {
96+
m := MonitoredListsBreakdown{
97+
Breakdown: globals.StatsData.UserAgentsMatches,
98+
}
99+
globals.StatsData.UserAgentsMatches = make(map[string]int)
100+
return m
101+
}
102+
87103
func GetStatsAndClear() Stats {
88104
globals.StatsData.StatsMutex.Lock()
89105
defer globals.StatsData.StatsMutex.Unlock()
@@ -100,6 +116,8 @@ func GetStatsAndClear() Stats {
100116
Blocked: globals.StatsData.AttacksBlocked,
101117
},
102118
},
119+
UserAgents: GetUserAgentsBreakdownAndClear(),
120+
IpAddresses: GetIpsBreakdownAndClear(),
103121
}
104122

105123
globals.StatsData.StartedAt = utils.GetTime()

lib/agent/go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,9 @@ require (
1717
golang.org/x/sys v0.30.0 // indirect
1818
golang.org/x/text v0.22.0 // indirect
1919
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
20+
golang.org/x/net v0.35.0 // indirect
21+
golang.org/x/sys v0.30.0 // indirect
22+
golang.org/x/text v0.22.0 // indirect
23+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
2024
gopkg.in/yaml.v3 v3.0.1 // indirect
2125
)

lib/agent/go.sum

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,38 @@ golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
1818
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
1919
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
2020
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
21+
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
22+
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
2123
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
2224
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2325
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
2426
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
27+
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
28+
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2529
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
2630
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
2731
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
2832
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
33+
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
34+
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
2935
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
3036
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
3137
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
3238
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
39+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=
40+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=
3341
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
3442
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
3543
google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
3644
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
37-
google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=
38-
google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
39-
google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA=
40-
google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
4145
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
4246
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
4347
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
4448
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
4549
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
4650
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
51+
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
52+
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
4753
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
4854
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4955
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

lib/agent/grpc/request.go

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,19 @@ func storeAttackStats(req *protos.AttackDetected) {
2626
}
2727
}
2828

29+
func storeMonitoredListsMatches(m *map[string]int, lists []string) {
30+
if *m == nil {
31+
*m = make(map[string]int)
32+
}
33+
34+
for _, list := range lists {
35+
if _, exists := (*m)[list]; !exists {
36+
(*m)[list] = 0
37+
}
38+
(*m)[list] += 1
39+
}
40+
}
41+
2942
func storeSinkStats(protoSinkStats *protos.MonitoredSinkStats) {
3043
globals.StatsData.StatsMutex.Lock()
3144
defer globals.StatsData.StatsMutex.Unlock()
@@ -176,6 +189,14 @@ func getRateLimitingStatus(method string, route string, user string, ip string)
176189
return &protos.RateLimitingStatus{Block: false}
177190
}
178191

192+
func getIpsList(ipsList map[string]IpBlocklist) map[string]*protos.IpBlockList {
193+
m := make(map[string]*protos.IpBlockList)
194+
for ipBlocklistSource, ipBlocklist := range ipsList {
195+
m[ipBlocklistSource] = &protos.IpBlockList{Description: ipBlocklist.Description, Ips: ipBlocklist.Ips}
196+
}
197+
return m
198+
}
199+
179200
func getCloudConfig(configUpdatedAt int64) *protos.CloudConfig {
180201
isBlockingEnabled := utils.IsBlockingEnabled()
181202

@@ -187,19 +208,15 @@ func getCloudConfig(configUpdatedAt int64) *protos.CloudConfig {
187208
}
188209

189210
cloudConfig := &protos.CloudConfig{
190-
ConfigUpdatedAt: globals.CloudConfig.ConfigUpdatedAt,
191-
BlockedUserIds: globals.CloudConfig.BlockedUserIds,
192-
BypassedIps: globals.CloudConfig.BypassedIps,
193-
BlockedIps: map[string]*protos.IpBlockList{},
194-
BlockedUserAgents: globals.CloudConfig.BlockedUserAgents,
195-
Block: isBlockingEnabled,
196-
}
197-
198-
for ipBlocklistSource, ipBlocklist := range globals.CloudConfig.BlockedIpsList {
199-
cloudConfig.BlockedIps[ipBlocklistSource] = &protos.IpBlockList{
200-
Description: ipBlocklist.Description,
201-
Ips: ipBlocklist.Ips,
202-
}
211+
ConfigUpdatedAt: globals.CloudConfig.ConfigUpdatedAt,
212+
BlockedUserIds: globals.CloudConfig.BlockedUserIds,
213+
BypassedIps: globals.CloudConfig.BypassedIps,
214+
BlockedIps: getIpsList(globals.CloudConfig.BlockedIpsList),
215+
BlockedUserAgents: globals.CloudConfig.BlockedUserAgents,
216+
MonitoredIps: getIpsList(globals.CloudConfig.MonitoredIpsList),
217+
MonitoredUserAgents: globals.CloudConfig.MonitoredUserAgents,
218+
UserAgentDetails: globals.CloudConfig.UserAgentDetails,
219+
Block: isBlockingEnabled,
203220
}
204221

205222
for _, endpoint := range globals.CloudConfig.Endpoints {

lib/agent/grpc/server.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,24 @@ func (s *server) OnMiddlewareInstalled(ctx context.Context, req *emptypb.Empty)
9090
return &emptypb.Empty{}, nil
9191
}
9292

93+
func (s *server) OnMonitoredIpMatch(ctx context.Context, req *protos.MonitoredIpMatch) (*emptypb.Empty, error) {
94+
log.Debugf("Received MonitoredIpMatch: %v", req.GetLists())
95+
globals.StatsData.StatsMutex.Lock()
96+
defer globals.StatsData.StatsMutex.Unlock()
97+
98+
storeMonitoredListsMatches(&globals.StatsData.IpAddressesMatches, req.GetLists())
99+
return &emptypb.Empty{}, nil
100+
}
101+
102+
func (s *server) OnMonitoredUserAgentMatch(ctx context.Context, req *protos.MonitoredUserAgentMatch) (*emptypb.Empty, error) {
103+
log.Debugf("Received MonitoredUserAgentMatch: %v", req.GetLists())
104+
globals.StatsData.StatsMutex.Lock()
105+
defer globals.StatsData.StatsMutex.Unlock()
106+
107+
storeMonitoredListsMatches(&globals.StatsData.UserAgentsMatches, req.GetLists())
108+
return &emptypb.Empty{}, nil
109+
}
110+
93111
var grpcServer *grpc.Server
94112

95113
func StartServer(lis net.Listener) {

0 commit comments

Comments
 (0)