Skip to content

Instantly share code, notes, and snippets.

@maesoser
Created July 1, 2025 09:01
Show Gist options
  • Save maesoser/c43a10ae665dfd0757ef38c2de86d16b to your computer and use it in GitHub Desktop.
Save maesoser/c43a10ae665dfd0757ef38c2de86d16b to your computer and use it in GitHub Desktop.
Cloudflare Workers script that fetches Microsoft Azure Service Tags JSON, extracts IP addresses for a specified service (like ApplicationInsightsAvailability), and automatically updates a Cloudflare IP list via API.
// Cloudflare Workers script to update IP lists from Microsoft Service Tags
// Configure these environment variables in your worker:
// - CLOUDFLARE_API_KEY: Your Cloudflare API key
// - CLOUDFLARE_ACCOUNT_ID: Your Cloudflare account ID
// - CLOUDFLARE_LIST_ID: The ID of the list to update
// - SERVICE_NAME: The service name to filter (e.g., "ApplicationInsightsAvailability")
const MICROSOFT_SERVICE_TAGS_URL = 'https://download.microsoft.com/download/7/1/d/71d86715-5596-4529-9b13-da13a5de5b63/ServiceTags_Public_20250616.json';
export default {
async updateList(serviceName, env){
console.log(`Processing service: ${serviceName}`);
// Fetch Microsoft Service Tags JSON
const serviceTagsResponse = await fetch(MICROSOFT_SERVICE_TAGS_URL);
if (!serviceTagsResponse.ok) {
throw new Error(`Failed to fetch service tags: ${serviceTagsResponse.status}`);
}
const serviceTagsData = await serviceTagsResponse.json();
console.log(`Fetched service tags with change number: ${serviceTagsData.changeNumber}`);
// Find the specified service in the values array
const targetService = serviceTagsData.values.find(service => service.name === serviceName);
if (!targetService) {
return new Response(
JSON.stringify({
error: `Service '${serviceName}' not found`,
availableServices: serviceTagsData.values.map(s => s.name).slice(0, 10) // Show first 10 for reference
}, null, 2),
{
status: 404,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Extract IP addresses from the service
const ipAddresses = targetService.properties.addressPrefixes || [];
console.log(`Found ${ipAddresses.length} IP addresses for ${serviceName}`);
if (ipAddresses.length === 0) {
return new Response(
JSON.stringify({
message: `No IP addresses found for service '${serviceName}'`,
service: targetService
}, null, 2),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Prepare items for Cloudflare list update
const listItems = ipAddresses.map(ip => ({
ip: ip,
comment: `${serviceName} - Updated from Microsoft Service Tags (Change #${serviceTagsData.changeNumber})`
}));
// Update Cloudflare list
const cloudflareApiUrl = `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/rules/lists/${env.CLOUDFLARE_LIST_ID}/items`;
const cloudflareResponse = await fetch(cloudflareApiUrl, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.CLOUDFLARE_API_KEY}`,
},
body: JSON.stringify(listItems)
});
const cloudflareResult = await cloudflareResponse.json();
if (!cloudflareResponse.ok) {
console.error('Cloudflare API error:', cloudflareResult);
return new Response(
JSON.stringify({
error: 'Failed to update Cloudflare list',
details: cloudflareResult
}, null, 2),
{
status: cloudflareResponse.status,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Return success response
return new Response(
JSON.stringify({
success: true,
message: `Successfully updated Cloudflare list with ${ipAddresses.length} IP addresses`,
service: serviceName,
changeNumber: serviceTagsData.changeNumber,
ipCount: ipAddresses.length,
listId: env.CLOUDFLARE_LIST_ID,
cloudflareResponse: cloudflareResult
}, null, 2),
{
status: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
}
}
);
},
async fetch(request, env, ctx) {
try {
// Handle CORS preflight requests
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
// Only allow GET and POST requests
if (!['GET', 'POST'].includes(request.method)) {
return new Response('Method not allowed', { status: 405 });
}
// Validate required environment variables
const requiredEnvVars = [ 'CLOUDFLARE_API_KEY', 'CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_LIST_ID'];
const missingVars = requiredEnvVars.filter(varName => !env[varName]);
if (missingVars.length > 0) {
return new Response(
JSON.stringify({
error: 'Missing required environment variables',
missing: missingVars
}, null, 2),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Get service name from query parameter or environment variable
const url = new URL(request.url);
const serviceName = url.searchParams.get('service') || env.SERVICE_NAME || 'ApplicationInsightsAvailability';
return this.updateList(serviceName, env);
} catch (error) {
console.error('Error in worker:', error);
return new Response(
JSON.stringify({
error: 'Internal server error',
message: error.message
}, null, 2),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
},
// Optional: Add a scheduled handler for automatic updates
async scheduled(controller, env, ctx) {
try {
// Create a fake request to trigger the main logic
const requiredEnvVars = [ 'CLOUDFLARE_API_KEY', 'CLOUDFLARE_ACCOUNT_ID', 'CLOUDFLARE_LIST_ID'];
const missingVars = requiredEnvVars.filter(varName => !env[varName]);
if (missingVars.length > 0) {
return new Response(
JSON.stringify({
error: 'Missing required environment variables',
missing: missingVars
}, null, 2),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Get service name from query parameter or environment variable
const serviceName = env.SERVICE_NAME || 'ApplicationInsightsAvailability';
console.error(this.updateList(serviceName, env));
} catch (error) {
console.error('Scheduled update failed:', error);
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment