Skip to content

Commit af92da5

Browse files
authored
Revert "Look up fastly IPs using cidr" (#55137)
1 parent ddb9196 commit af92da5

File tree

4 files changed

+26
-37
lines changed

4 files changed

+26
-37
lines changed

package-lock.json

Lines changed: 2 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,6 @@
286286
"html-entities": "^2.5.2",
287287
"http-proxy-middleware": "3.0.3",
288288
"imurmurhash": "^0.1.4",
289-
"ipaddr.js": "^2.2.0",
290289
"is-svg": "5.0.0",
291290
"javascript-stringify": "^2.1.0",
292291
"js-cookie": "^3.0.1",

src/shielding/lib/fastly-ips.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
// Logic to get and store the current list of public Fastly IPs from the Fastly API: https://www.fastly.com/documentation/reference/api/utils/public-ip-list/
22

3-
import ipaddr, { IPv4, IPv6 } from 'ipaddr.js'
4-
5-
type IPRangeArr = [IPv4 | IPv6, number][]
6-
73
// Default returned from ➜ curl "https://api.fastly.com/public-ip-list"
8-
export const DEFAULT_FASTLY_IPS: IPRangeArr = [
4+
export const DEFAULT_FASTLY_IPS: string[] = [
95
'23.235.32.0/20',
106
'43.249.72.0/22',
117
'103.244.50.0/24',
@@ -25,21 +21,22 @@ export const DEFAULT_FASTLY_IPS: IPRangeArr = [
2521
'185.31.16.0/22',
2622
'199.27.72.0/21',
2723
'199.232.0.0/16',
28-
].map((cidr) => ipaddr.parseCIDR(cidr))
24+
]
2925

30-
let ipRangeCache: IPRangeArr = []
26+
let ipCache: string[] = []
3127

32-
export async function getPublicFastlyIPs(): Promise<IPRangeArr> {
28+
export async function getPublicFastlyIPs(): Promise<string[]> {
3329
// Don't fetch the list in dev & testing, just use the defaults
3430
if (process.env.NODE_ENV !== 'production') {
35-
ipRangeCache = DEFAULT_FASTLY_IPS
31+
ipCache = DEFAULT_FASTLY_IPS
3632
}
3733

38-
if (ipRangeCache.length) {
39-
return ipRangeCache
34+
if (ipCache.length) {
35+
return ipCache
4036
}
4137

4238
const endpoint = 'https://api.fastly.com/public-ip-list'
39+
let ips: string[] = []
4340
let attempt = 0
4441

4542
while (attempt < 3) {
@@ -50,8 +47,8 @@ export async function getPublicFastlyIPs(): Promise<IPRangeArr> {
5047
}
5148
const data = await response.json()
5249
if (data && Array.isArray(data.addresses)) {
53-
ipRangeCache = data.addresses.map((cidr: string) => ipaddr.parseCIDR(cidr))
54-
return ipRangeCache
50+
ips = data.addresses
51+
break
5552
} else {
5653
throw new Error('Invalid response structure')
5754
}
@@ -60,22 +57,25 @@ export async function getPublicFastlyIPs(): Promise<IPRangeArr> {
6057
`Failed to fetch Fastly IPs: ${error.message}. Retrying ${3 - attempt} more times`,
6158
)
6259
attempt++
60+
if (attempt >= 3) {
61+
ips = DEFAULT_FASTLY_IPS
62+
}
6363
}
6464
}
6565

66-
ipRangeCache = DEFAULT_FASTLY_IPS
67-
return ipRangeCache
66+
ipCache = ips
67+
return ips
6868
}
6969

7070
// The IPs we check in the rate-limiter are in the form `X.X.X.X`
7171
// But the IPs returned from the Fastly API are in the form `X.X.X.X/Y`
7272
// For an IP in the rate-limiter, we want `X.X.X.*` to match `X.X.X.X/Y`
7373
export async function isFastlyIP(ip: string): Promise<boolean> {
7474
// If IPs aren't initialized, fetch them
75-
if (!ipRangeCache.length) {
75+
if (!ipCache.length) {
7676
await getPublicFastlyIPs()
7777
}
78-
if (!ip) return false // localhost
79-
const addr = ipaddr.parse(ip)
80-
return ipRangeCache.some((range) => addr.match(range))
78+
const parts = ip.split('.')
79+
const prefix = parts.slice(0, 3).join('.')
80+
return ipCache.some((fastlyIP) => fastlyIP.startsWith(prefix))
8181
}

src/shielding/tests/shielding.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, test } from 'vitest'
22

33
import { SURROGATE_ENUMS } from '@/frame/middleware/set-fastly-surrogate-key.js'
44
import { get } from '@/tests/helpers/e2etest.js'
5+
import { DEFAULT_FASTLY_IPS } from '@/shielding/lib/fastly-ips'
56

67
describe('honeypotting', () => {
78
test('any GET with survey-vote and survey-token query strings is 400', async () => {
@@ -104,7 +105,7 @@ describe('rate limiting', () => {
104105
headers: {
105106
// Rate limiting only happens in production, so we need to
106107
// make the environment look like production.
107-
'fastly-client-ip': '0.0.0.0',
108+
'fastly-client-ip': 'abc',
108109
},
109110
})
110111
expect(res.statusCode).toBe(200)
@@ -117,7 +118,7 @@ describe('rate limiting', () => {
117118
{
118119
const res = await get('/robots.txt?foo=buzz', {
119120
headers: {
120-
'fastly-client-ip': '0.0.0.0',
121+
'fastly-client-ip': 'abc',
121122
},
122123
})
123124
expect(res.statusCode).toBe(200)
@@ -141,7 +142,8 @@ describe('rate limiting', () => {
141142
// Fastly IPs are in the form `X.X.X.X/Y`
142143
// Rate limited IPs are in the form `X.X.X.X`
143144
// Where the last X could be any 2-3 digit number
144-
const mockFastlyIP = '23.235.32.0'
145+
const mockFastlyIP =
146+
DEFAULT_FASTLY_IPS[0].split('.').slice(0, 3).join('.') + `.${Math.floor(Math.random() * 100)}`
145147
// Cookies only allows 1 request per minute
146148
const res1 = await get('/api/cookies', {
147149
headers: {

0 commit comments

Comments
 (0)