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  }