github.com/gramework/gramework@v1.8.1-0.20231027140105-82555c9057f5/app_protection.go (about) 1 package gramework 2 3 import ( 4 "net" 5 "sync" 6 "sync/atomic" 7 ) 8 9 // Protect enables Gramework Protection for routes registered after Protect() call. 10 // 11 // Protects all routes, that prefixed with given enpointPrefix. 12 // For example: 13 // 14 // app := gramework.New() 15 // app.GET("/internal/status", serveStatus) // will **not be** protected, .Protected() isn't called yet 16 // app.Protect("/internal") 17 // registerYourInternalRoutes(app.Sub("/internal")) // all routes here will be protected 18 // 19 // Any blacklisted ip can't access protected enpoints via any method. 20 // Blacklist can work automatically, manually or both. To disable automatic blacklist do App.MaxHackAttemts(-1). 21 // Automatic blacklist bans suspected IP after App.MaxHackAttempts(). This behaviour is disabled for whitelisted 22 // ip. 23 // 24 // See also App.Whitelist(), App.Untrust(), App.Blacklist(), App.Suspect(), App.MaxHackAttempts(), 25 // Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 26 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 27 // Context.SuspectsHackAttempts() 28 func (app *App) Protect(endpointPrefix string) { 29 if app.trustedIP == nil { 30 app.trustedIP = &ipList{ 31 list: make(map[string]struct{}), 32 mu: &sync.RWMutex{}, 33 } 34 } 35 if app.untrustedIP == nil { 36 app.untrustedIP = &ipList{ 37 list: make(map[string]struct{}), 38 mu: &sync.RWMutex{}, 39 } 40 } 41 if app.suspectedIP == nil { 42 app.suspectedIP = &suspectsList{ 43 list: make(map[string]*suspect), 44 mu: &sync.RWMutex{}, 45 } 46 } 47 48 if app.protectedPrefixes == nil { 49 app.protectedPrefixes = make(map[string]struct{}) 50 app.protectedEndpoints = make(map[string]struct{}) 51 } 52 53 app.protectedPrefixes[endpointPrefix] = struct{}{} 54 } 55 56 func nilHijackHandler(c net.Conn) { 57 } 58 59 func (app *App) protectionMiddleware(handler func(*Context)) func(ctx *Context) { 60 return func(ctx *Context) { 61 if ctx.IsBlacklisted() { 62 // force closing of the connection ASAP 63 ctx.Hijack(nilHijackHandler) 64 return 65 } 66 67 handler(ctx) 68 } 69 } 70 71 // Whitelist adds given ip to Gramework Protection trustedIP list. 72 // To remove IP from whitelist, call App.Untrust() 73 // 74 // See also App.Protect(), App.Untrust(), App.Blacklist(), App.Suspect(), App.MaxHackAttempts(), 75 // Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 76 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 77 // Context.SuspectsHackAttempts() 78 func (app *App) Whitelist(ip net.IP) (ok bool) { 79 if ip == nil { 80 return false 81 } 82 if ip.IsLoopback() { 83 return true 84 } 85 86 ipHash := app.prepareIPListKey(ip) 87 88 // now we trust this ip 89 app.trustedIP.mu.Lock() 90 app.trustedIP.list[ipHash] = struct{}{} 91 app.trustedIP.mu.Unlock() 92 93 // unban this ip 94 app.untrustedIP.mu.Lock() 95 delete(app.untrustedIP.list, ipHash) 96 app.untrustedIP.mu.Unlock() 97 98 // whitelisted ip can't be suspected 99 app.suspectedIP.mu.Lock() 100 delete(app.suspectedIP.list, ipHash) 101 app.suspectedIP.mu.Unlock() 102 return true 103 } 104 105 // Untrust removes given ip from trustedIP list, that 106 // enables protection of Gramework Protection enabled endpoints for given ip too. 107 // Opposite of App.Whitelist(). 108 // 109 // See also App.Protect(), App.Whitelist(), App.Blacklist(), App.Suspect(), App.MaxHackAttempts(), 110 // Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 111 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 112 // Context.SuspectsHackAttempts() 113 func (app *App) Untrust(ip net.IP) (ok bool) { 114 if ip == nil { 115 return false 116 } 117 ipHash := app.prepareIPListKey(ip) 118 119 // now we don't trust this ip 120 app.trustedIP.mu.Lock() 121 delete(app.trustedIP.list, ipHash) 122 app.trustedIP.mu.Unlock() 123 return true 124 } 125 126 // Blacklist adds given ip to untrustedIP list, if it is not whitelisted. Any ip blacklisted with 127 // Gramework Protection can't access protected enpoints via any method. 128 // 129 // See also App.Protect(), App.Whitelist(), App.Untrust(), App.Suspect(), App.MaxHackAttempts(), 130 // Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 131 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 132 // Context.SuspectsHackAttempts() 133 func (app *App) Blacklist(ip net.IP) (ok bool) { 134 if ip == nil { 135 return false 136 } 137 138 ipHash := app.prepareIPListKey(ip) 139 140 app.trustedIP.mu.RLock() 141 if _, ok := app.trustedIP.list[ipHash]; ok { 142 app.trustedIP.mu.RUnlock() 143 return false 144 } 145 app.trustedIP.mu.RUnlock() 146 147 // ban this ip 148 app.untrustedIP.mu.Lock() 149 app.untrustedIP.list[ipHash] = struct{}{} 150 app.untrustedIP.mu.Unlock() 151 152 // we don't need to suspect already banned ip 153 app.suspectedIP.mu.Lock() 154 delete(app.suspectedIP.list, ipHash) 155 app.suspectedIP.mu.Unlock() 156 return true 157 } 158 159 // Suspect adds given ip to Gramework Protection suspectedIP list. 160 // 161 // See also App.Protect(), App.Untrust(), App.Blacklist(), App.Suspect(), App.MaxHackAttempts(), 162 // Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 163 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 164 // Context.SuspectsHackAttempts() 165 func (app *App) Suspect(ip net.IP) (ok bool) { 166 ipHash := app.prepareIPListKey(ip) 167 168 app.trustedIP.mu.RLock() 169 if _, ok := app.trustedIP.list[ipHash]; ok { 170 app.trustedIP.mu.RUnlock() 171 return false 172 } 173 app.trustedIP.mu.RUnlock() 174 175 // suspect this ip 176 app.suspectedIP.mu.Lock() 177 app.suspectedIP.list[ipHash] = &suspect{ 178 hackAttempts: 0, 179 } 180 app.suspectedIP.mu.Unlock() 181 return true 182 } 183 184 // MaxHackAttempts sets new max hack attempts for blacklist triggering in the Gramework Protection. 185 // If 0 passed, MaxHackAttempts just returns current value 186 // without setting a new one. 187 // If -1 passed, automatic blacklist disabled. 188 // This function is threadsafe and atomic. 189 // 190 // See `ctx.Whitelist()`, `ctx.Blacklist()` and `ctx.Suspect()` for manual Gramework Protection control. 191 // 192 // See also App.Protect(), App.Whitelist(), App.Blacklist(), App.Suspect(), 193 // Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 194 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 195 // Context.SuspectsHackAttempts() 196 func (app *App) MaxHackAttempts(attempts int32) (oldValue int32) { 197 oldValue = atomic.LoadInt32(app.maxHackAttempts) 198 if attempts != 0 && atomic.CompareAndSwapInt32(app.maxHackAttempts, oldValue, attempts) { 199 app.internalLog. 200 WithField("old", oldValue). 201 WithField("new", attempts). 202 Infof("[Gramework Protection] Updated max hack attempts") 203 } 204 return 205 } 206 207 // IsWhitelisted checks if we have current client in Gramework Protection trustedIP list. 208 // Use ctx.Whitelist() to add current client to trusted list. 209 // 210 // See also App.Protect(), App.Whitelist(), App.Blacklist(), App.Suspect(), 211 // App.MaxHackAttempts(), Context.IsBlacklisted(), Context.IsSuspect(), 212 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 213 // Context.SuspectsHackAttempts() 214 func (ctx *Context) IsWhitelisted() (isWhitelisted bool) { 215 if ctx.RemoteIP().IsLoopback() { 216 return true 217 } 218 ctx.App.trustedIP.mu.RLock() 219 _, isWhitelisted = ctx.App.trustedIP.list[ctx.remoteIPHash()] 220 ctx.App.trustedIP.mu.RUnlock() 221 return 222 } 223 224 // IsBlacklisted checks if we have current client in Gramework Protection untrustedIP list. 225 // Use ctx.Blacklist() to add current client to untrustedIP list. 226 // 227 // See also App.Protect(), App.Whitelist(), App.Blacklist(), App.Suspect(), 228 // App.MaxHackAttempts(), Context.IsWhitelisted(), Context.IsSuspect(), 229 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 230 // Context.SuspectsHackAttempts() 231 func (ctx *Context) IsBlacklisted() (isBlacklisted bool) { 232 if ctx.IsWhitelisted() { 233 return false 234 } 235 ctx.App.untrustedIP.mu.RLock() 236 _, isBlacklisted = ctx.App.untrustedIP.list[ctx.remoteIPHash()] 237 ctx.App.untrustedIP.mu.RUnlock() 238 return 239 } 240 241 // IsSuspect checks if we have current client in Gramework Protection suspectedIP list. 242 // Use ctx.Suspect() to add current client to suspectedIP list. 243 // 244 // See also App.Protect(), App.Whitelist(), App.Blacklist(), App.Suspect(), 245 // App.MaxHackAttempts(), Context.IsWhitelisted(), Context.IsBlacklisted(), 246 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 247 // Context.SuspectsHackAttempts() 248 func (ctx *Context) IsSuspect() (isSuspect bool) { 249 if ctx.IsWhitelisted() { 250 return false 251 } 252 ctx.App.suspectedIP.mu.RLock() 253 _, isSuspect = ctx.App.suspectedIP.list[ctx.remoteIPHash()] 254 ctx.App.suspectedIP.mu.RUnlock() 255 return 256 } 257 258 // Whitelist adds given ip to trustedIP list of the Gramework Protection. 259 // To remove IP from whitelist, call App.Untrust() 260 // 261 // See also App.Protect(), App.Untrust(), App.Blacklist(), App.Suspect(), App.MaxHackAttempts(), 262 // App.Whitelist(), Context.Untrust(), Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 263 // Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 264 // Context.SuspectsHackAttempts() 265 func (ctx *Context) Whitelist() (ok bool) { 266 return ctx.App.Whitelist(ctx.RemoteIP()) 267 } 268 269 // Untrust deletes given ip from trustedIP list, that 270 // enables protection of Protect()'ed endpoints for given ip too. 271 // Opposite of Context.Whitelist(). 272 // 273 // See also App.Protect(), App.Whitelist(), App.Blacklist(), App.Suspect(), App.MaxHackAttempts(), 274 // App.Untrust(), Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 275 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 276 // Context.SuspectsHackAttempts() 277 func (ctx *Context) Untrust() (ok bool) { 278 return ctx.App.Untrust(ctx.RemoteIP()) 279 } 280 281 // Blacklist adds given ip to untrustedIP list, if it is not whitelisted. Any blacklisted ip can't 282 // access protected enpoints via any method. 283 // 284 // See also App.Protect(), App.Whitelist(), App.Untrust(), App.Suspect(), App.MaxHackAttempts(), 285 // App.Blacklist(), Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 286 // Context.Whitelist(), Context.Suspect(), Context.HackAttemptDetected(), 287 // Context.SuspectsHackAttempts() 288 func (ctx *Context) Blacklist() (ok bool) { 289 return ctx.App.Blacklist(ctx.RemoteIP()) 290 } 291 292 // Suspect adds current client ip to Gramework Protection suspectedIP list. 293 // 294 // See also App.Protect(), App.Untrust(), App.Blacklist(), App.Suspect(), App.MaxHackAttempts(), 295 // Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 296 // Context.Whitelist(), Context.Blacklist(), Context.Suspect(), Context.HackAttemptDetected(), 297 // Context.SuspectsHackAttempts() 298 func (ctx *Context) Suspect() (ok bool) { 299 return ctx.App.Suspect(ctx.RemoteIP()) 300 } 301 302 // remoteIPHash is a shortcut for app.prepareIPListKey() function that 303 // calculates hash for the []byte, which is what the ip is 304 func (ctx *Context) remoteIPHash() string { 305 return ctx.App.prepareIPListKey(ctx.RemoteIP()) 306 } 307 308 // HackAttemptDetected adds given ip to Gramework Protection suspectedIP list. 309 // Use it when you detected app-level hack attempt from current client. 310 // 311 // See also App.Protect(), App.Whitelist(), App.Untrust(), App.Suspect(), App.MaxHackAttempts(), 312 // App.Blacklist(), Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 313 // Context.Whitelist(), Context.Suspect(), Context.Blacklist(), 314 // Context.SuspectsHackAttempts() 315 func (ctx *Context) HackAttemptDetected() { 316 if ctx.IsWhitelisted() { 317 return 318 } 319 ipHash := ctx.remoteIPHash() 320 321 ctx.App.suspectedIP.mu.Lock() 322 if s, ok := ctx.App.suspectedIP.list[ipHash]; !ok || s == nil { 323 ctx.App.suspectedIP.list[ipHash] = &suspect{ 324 hackAttempts: 1, 325 } 326 ctx.App.suspectedIP.mu.Unlock() 327 return 328 } 329 // release lock ASAP 330 ctx.App.suspectedIP.mu.Unlock() 331 atomic.AddInt32(&ctx.App.suspectedIP.list[ipHash].hackAttempts, 1) 332 } 333 334 // SuspectsHackAttempts returns hack attempts detected with Gramework Protection 335 // both automatically and manually by calling Context.HackAttemptDetected(). 336 // For any whitelisted ip this function will return 0. 337 // 338 // See also App.Protect(), App.Whitelist(), App.Untrust(), App.Suspect(), App.MaxHackAttempts(), 339 // App.Blacklist(), Context.IsWhitelisted(), Context.IsBlacklisted(), Context.IsSuspect(), 340 // Context.Whitelist(), Context.Suspect(), Context.Blacklist(), 341 // Context.HackAttemptDetected() 342 func (ctx *Context) SuspectsHackAttempts() (attempts int32) { 343 if ctx.IsWhitelisted() { 344 return zero 345 } 346 ctx.App.suspectedIP.mu.RLock() 347 if s, ok := ctx.App.suspectedIP.list[ctx.remoteIPHash()]; ok && s != nil { 348 attempts = s.hackAttempts 349 } 350 ctx.App.suspectedIP.mu.RUnlock() 351 return 352 } 353 354 func (app *App) prepareIPListKey(ip net.IP) string { 355 if ip == nil { 356 // we should ban any invalid remoteIP headers 357 // to ban this type of attacks 358 return "" 359 } 360 return ip.String() 361 }