Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save JoeyBurzynski/416cf2370ff03e980296054830e65c1e to your computer and use it in GitHub Desktop.
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]
// 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