Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions caddy/Caddyfile.prod
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@ oullin.io {
format json
}

route /api/generate-signature/* {
@allowed {
# Allow requests only from the VPS itself (localhost).
ip_range 127.0.0.1 ::1
}

# If the remote IP is not in the allowed range, abort the connection.
abort not @allowed
}

# API handler.
# - Reverse-proxy all requests to the Go API, forwarding Host + auth headers.
# - to: Tell Caddy which upstream to send to.
Expand Down
20 changes: 20 additions & 0 deletions pkg/middleware/public_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ func (p PublicMiddleware) Handle(next http.ApiHandler) http.ApiHandler {
return mwguards.RateLimitedError("Too many requests", "Too many requests for key: "+limiterKey)
}

if err := p.HasInvalidIP(r); err != nil {
p.rateLimiter.Fail(limiterKey)

return err
}

vt := mwguards.NewValidTimestamp(ts, p.now)
if err := vt.Validate(p.clockSkew, p.disallowFuture); err != nil {
p.rateLimiter.Fail(limiterKey)
Expand All @@ -83,6 +89,20 @@ func (p PublicMiddleware) Handle(next http.ApiHandler) http.ApiHandler {
}
}

func (p PublicMiddleware) HasInvalidIP(r *baseHttp.Request) *http.ApiError {
ip := portal.ParseClientIP(r)

if ip == "" {
return mwguards.InvalidRequestError("Clients IPs are required to access this endpoint", "")
}

if p.isProduction && ip != p.allowedIP {
return mwguards.InvalidRequestError("The given IP is not allowed", "unauthorised ip: "+ip)
}
Comment on lines +99 to +101

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current IP address check uses a direct string comparison, which can be brittle. For instance, it wouldn't correctly handle different string representations of the same IP address (e.g., IPv4 vs. IPv4-mapped IPv6 addresses).

Furthermore, the implementation only allows a single IP, which is a regression from the previous Caddy configuration that supported an ip_range (like 127.0.0.1 ::1 for both IPv4 and IPv6 localhost).

To make this more robust and flexible, I suggest parsing the IP strings into net.IP objects and using the Equal() method for comparison. To support multiple allowed IPs, p.allowedIP could be treated as a comma-separated list.

Here's a suggested implementation that incorporates these improvements. Note that you will need to add import "net" to the file.

if p.isProduction {
		clientIP := net.ParseIP(ip)
		if clientIP == nil {
			return mwguards.InvalidRequestError("The given IP is not allowed", "unauthorised ip: "+ip)
		}

		isAllowed := false
		allowedIPs := strings.Split(p.allowedIP, ",")
		for _, allowedIPStr := range allowedIPs {
			allowedIP := net.ParseIP(strings.TrimSpace(allowedIPStr))
			if allowedIP != nil && clientIP.Equal(allowedIP) {
				isAllowed = true
				break
			}
		}

		if !isAllowed {
			return mwguards.InvalidRequestError("The given IP is not allowed", "unauthorised ip: "+ip)
		}
	}


return nil
}

func (p PublicMiddleware) GuardDependencies() *http.ApiError {
missing := []string{}

Expand Down
Loading