Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add per URI rate limiting (DDoS protection) #109

Merged
merged 1 commit into from
Dec 20, 2024
Merged

Add per URI rate limiting (DDoS protection) #109

merged 1 commit into from
Dec 20, 2024

Conversation

mrheinen
Copy link
Owner

@mrheinen mrheinen commented Dec 20, 2024

User description

This is tracked in #107. With this change you can configure per URI limits to prevent DDoS attacks.


PR Type

Enhancement


Description

Enhanced rate limiting system to prevent DDoS attacks by implementing URI-based rate limiting:

  • Added per-URI rate limiting alongside existing per-IP rate limiting
  • Introduced new configuration options:
    • max_uri_requests_per_window (default: 2000)
    • max_uri_requests_per_bucket (default: 100)
  • Implemented separate tracking and metrics for IP and URI rate limits
  • Added comprehensive test coverage for new functionality
  • Updated error types and metrics to distinguish between IP and URI rate limit violations
  • Added mutex locks to ensure thread safety for both IP and URI rate buckets

This change addresses the DDoS vulnerability where multiple IPs could collectively overwhelm a specific URI endpoint while staying under individual IP limits.


Changes walkthrough 📝

Relevant files
Enhancement
ratelimit.go
Implement URI-based rate limiting for DDoS protection       

pkg/backend/ratelimit/ratelimit.go

  • Added URI-based rate limiting alongside IP-based limiting
  • Split rate limiting logic into separate IP and URI handling functions
  • Added new error types for IP and URI rate limit violations
  • Implemented separate mutex locks for IP and URI rate buckets
  • +103/-41
    metrics.go
    Add separate metrics for IP and URI rate limiting               

    pkg/backend/ratelimit/metrics.go

  • Split rate bucket metrics into separate IP and URI gauges
  • Added new Prometheus metrics for URI rate limiting
  • +12/-6   
    Configuration changes
    config.go
    Add URI rate limit configuration parameters                           

    pkg/backend/config.go

  • Added new configuration options for URI rate limiting
  • Split rate limit configuration into IP and URI specific parameters
  • +7/-5     
    Tests
    ratelimit_test.go
    Add test coverage for URI rate limiting functionality       

    pkg/backend/ratelimit/ratelimit_test.go

  • Added comprehensive test cases for URI rate limiting
  • Updated existing IP rate limit tests
  • Added test coverage for bucket creation and limit violations
  • +239/-8 

    💡 PR-Agent usage: Comment /help "your question" on any pull request to receive relevant information

    Copy link
    Contributor

    PR Reviewer Guide 🔍

    Here are some key observations to aid the review process:

    🎫 Ticket compliance analysis ✅

    107 - Fully compliant

    Compliant requirements:

    • Added destination URI-based rate limiting
    • Implemented combined IP and URI rate limiting
    • Added configuration for URI rate limits
    ⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
    🧪 PR contains tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Race Condition

    The rate limiter uses separate mutexes for IP and URI buckets, but AllowRequest() checks both without holding both locks. This could lead to race conditions if requests are processed concurrently.

    func (r *WindowRateLimiter) AllowRequest(req *models.Request) (bool, error) {
    	ret, err := r.allowRequestForIP(req)
    	if !ret {
    		return ret, err
    	}
    
    	return r.allowRequestForURI(req)
    }
    Configuration Validation

    The new URI rate limit configuration values should be validated to ensure max_uri_requests_per_bucket cannot exceed max_uri_requests_per_window.

      max_ip_requests_per_window: 500
      max_ip_requests_per_bucket: 50
      max_uri_requests_per_window: 1500
      max_uri_requests_per_bucket: 200

    Copy link
    Contributor

    PR Code Suggestions ✨

    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Score
    General
    Use read-write mutex to improve performance by allowing concurrent read operations

    Consider using sync.RWMutex instead of sync.Mutex for the rate limiter locks since
    most operations are reads (checking limits) rather than writes. This can improve
    performance by allowing concurrent reads.

    pkg/backend/ratelimit/ratelimit.go [51-63]

     type WindowRateLimiter struct {
         ...
    -    rateIPMu  sync.Mutex
    -    rateURIMu sync.Mutex
    +    rateIPMu  sync.RWMutex
    +    rateURIMu sync.RWMutex
         ...
     }
    Suggestion importance[1-10]: 7

    Why: Using RWMutex instead of Mutex would allow multiple concurrent reads while maintaining exclusive write access, which could improve performance since rate limiting involves more read operations than writes.

    7
    Use buffered channels to prevent potential goroutine leaks

    Consider using a buffered channel for bgChan to prevent potential goroutine leaks in
    case the receiver is not ready when a message is sent.

    pkg/backend/ratelimit/ratelimit.go [63]

    -bgChan: make(chan bool),
    +bgChan: make(chan bool, 1),
    • Apply this suggestion
    Suggestion importance[1-10]: 6

    Why: Using a buffered channel prevents potential goroutine leaks by ensuring sends won't block if the receiver isn't ready, which is important for background operations.

    6
    Use named constants instead of magic numbers to improve code maintainability

    Consider using constants for the default configuration values instead of magic
    numbers in the test functions to improve maintainability and clarity.

    pkg/backend/ratelimit/ratelimit.go [284-292]

    +const (
    +    testWindowDuration = 5 * time.Second
    +    testBucketDuration = time.Second
    +    testMaxIPReqWindow = 4
    +    testMaxIPReqBucket = 2
    +    testMaxURIReqWindow = 10
    +    testMaxURIReqBucket = 5
    +)
    +
     r := NewWindowRateLimiter(
    -    time.Second*5, // window
    -    time.Second,   // bucket duration
    -    4,             // max requests per window
    -    2,             // max requests per bucket
    -    10,            // max URI requests per window
    -    5,             // max URI requests per bucket
    +    testWindowDuration,
    +    testBucketDuration,
    +    testMaxIPReqWindow,
    +    testMaxIPReqBucket,
    +    testMaxURIReqWindow,
    +    testMaxURIReqBucket,
         CreateRatelimiterMetrics(prometheus.NewRegistry()),
     )
    • Apply this suggestion
    Suggestion importance[1-10]: 5

    Why: Using named constants instead of magic numbers in tests improves code readability and maintainability by making the values' purposes clear and centralizing test configuration.

    5

    Copy link

    Failed to generate code suggestions for PR

    @mrheinen mrheinen merged commit 6af1bf9 into main Dec 20, 2024
    2 checks passed
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    1 participant