Last active
May 11, 2024 13:58
-
-
Save JoeyBurzynski/416cf2370ff03e980296054830e65c1e to your computer and use it in GitHub Desktop.
Resolving Cloudflare Worker IP Issues [Replace Cloudflare Worker IP with Real Client IP in X-Forwarded-For HTTP Header]
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Cloudflare Worker Sandbox Examples | |
// Learning here, not intended for production use. | |
// https://cloudflareworkers.com/#6bc84bcddcf251074b41adba568a9284:https://tutorial.cloudflareworkers.com | |
addEventListener('fetch', event => { | |
event.respondWith(handleRequest(event.request)); | |
}) | |
/** | |
* Fetch and log a given request object | |
* @param {Request} request | |
*/ | |
async function handleRequest(request) { | |
console.clear() | |
console.log("\nHTTP Request:") | |
console.log(request) | |
// Get the true client IP address from the "CF-Connecting-IP" header. | |
const clientIP = request.headers.get("CF-Connecting-IP"); | |
console.log("\nHTTP Request Headers:") | |
console.log(`Real Client IP: ${clientIP}`) | |
console.log(`X-Forwarded-For: ${request.headers.get("X-Forwarded-For")}`) | |
console.log(`CF-Connecting-IP: ${request.headers.get("CF-Connecting-IP")}\n`) | |
// Call the `replaceWorkerIPWithClientIP()` function to replace the Worker IP with the true client IP. | |
const modifiedRequest = replaceWorkerIPWithClientIP(request); | |
console.log("\nModified HTTP Request Headers") | |
console.log(`X-Forwarded-For: ${request.headers.get("X-Forwarded-For")}`) | |
console.log(`CF-Connecting-IP: ${request.headers.get("CF-Connecting-IP")}\n`) | |
// Fetch Modified HTTP Request | |
const response = await fetch(modifiedRequest) | |
console.log("\nHTTP Response:") | |
console.log(response) | |
console.log(`X-Forwarded-For: ${response.headers.get("x-forwarded-for")}`) | |
console.log(`CF-Connecting-IP: ${response.headers.get("cf-connecting-ip")}\n`) | |
// Define any custom headers to be added. | |
const customHeaders = { | |
"M-Client-IP": `${clientIP}`, | |
"M-Custom-Header": `true`, | |
}; | |
// Call the `addCustomHeadersToResponse()` function to add custom headers to the response. | |
const modifiedResponse = addCustomHeadersToResponse(response, customHeaders); | |
logResponseHeaders(modifiedResponse); | |
// Return the modified response with custom headers added. | |
const finalResponse = await prettyPrintResponseHeaders(modifiedResponse) | |
return finalResponse | |
} | |
/** | |
* Log all HTTP headers on a Cloudflare Response object. | |
* | |
* @param {Response} response - The response object containing the headers. | |
*/ | |
function logResponseHeaders(response) { | |
// Clone the original response object. | |
const clonedResponse = new Response(response.body, response); | |
// Create a new Headers object based on the original response headers. | |
const headers = new Headers(clonedResponse.headers); | |
console.log("\n==============================[ HTTP Response Headers ]==============================") | |
// Iterate through the headers and log each header key-value pair. | |
for (const [key, value] of headers) { | |
console.log(`${key}: ${value}`); | |
} | |
} | |
/** | |
* Clones the Response object, adds custom HTTP headers, and returns the new Response object with custom headers. | |
* | |
* @param {Response} response - The original response object. | |
* @param {Object} customHeaders - An object containing key-value pairs of custom headers to be added. | |
* @returns {Response} - The new Response object with custom headers added. | |
*/ | |
function addCustomHeadersToResponse(response, customHeaders) { | |
// Clone the original response object. | |
const clonedResponse = new Response(response.body, response); | |
// Create a new Headers object based on the original response headers. | |
const newHeaders = new Headers(clonedResponse.headers); | |
// Iterate through the custom headers, and add each header to the new Headers object. | |
for (const [key, value] of Object.entries(customHeaders)) { | |
newHeaders.set(key, value); | |
} | |
// Create and return the new Response object with custom headers added. | |
return new Response(clonedResponse.body, { | |
status: clonedResponse.status, | |
statusText: clonedResponse.statusText, | |
headers: newHeaders, | |
}); | |
} | |
/** | |
* Fetches the response headers from the origin server, pretty-prints them, and returns them as the response. | |
* | |
* @param {Request} request - The incoming request. | |
* @returns {Promise<Response>} - A promise that resolves to the response with pretty-printed headers. | |
*/ | |
async function prettyPrintResponseHeaders(response) { | |
// Clone the original response object. | |
const clonedResponse = new Response(response.body, response); | |
// Create a new Headers object based on the original response headers. | |
const headers = new Headers(clonedResponse.headers); | |
// Create an array to store the formatted header strings. | |
const formattedHeaders = []; | |
// Iterate through the headers, and add each header as a formatted string to the array. | |
for (const [key, value] of headers) { | |
formattedHeaders.push(`${key}: ${value}`); | |
} | |
// Sort the formatted headers alphabetically by header name. | |
formattedHeaders.sort(); | |
// Join the formatted header strings with newlines to create the pretty-printed headers. | |
const prettyHeaders = formattedHeaders.join("\n"); | |
// Return the pretty-printed headers as the response. | |
return new Response(prettyHeaders, { | |
headers: { | |
"Content-Type": "text/plain" | |
}, | |
}); | |
} | |
/** | |
* Replace the Cloudflare Worker IP address with the true client IP | |
* in the "X-Forwarded-For" header of all incoming HTTP requests and/or subrequests. | |
* | |
* @param {Request} request - The request or subrequest. | |
* @returns {Request} - The modified request with the true client IP in the "X-Forwarded-For" header. | |
*/ | |
function replaceWorkerIPWithClientIP(request) { | |
// Get the true client IP address from the "CF-Connecting-IP" header. | |
const clientIP = request.headers.get("CF-Connecting-IP"); | |
// Get the existing "X-Forwarded-For" header value, if it exists. | |
const existingXForwardedFor = request.headers.get("X-Forwarded-For"); | |
// Set the new "X-Forwarded-For" header value based on the existing value and the true client IP. | |
const newXForwardedFor = existingXForwardedFor ? | |
`${clientIP}, ${existingXForwardedFor}` : | |
clientIP; | |
// Best practice is to always use the original request to construct the new request | |
// to clone all the attributes. Applying the URL also requires a constructor | |
// since once a Request has been constructed, its URL is immutable. | |
// Create a new request object based on the original request, replacing the "X-Forwarded-For" header. | |
const modifiedRequest = new Request(request, { | |
headers: { | |
...request.headers, | |
"X-Forwarded-For": newXForwardedFor, | |
}, | |
}); | |
// Return the modified request. | |
return modifiedRequest; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment