Skip to content

Commit 0f93f7b

Browse files
authored
feat: Acl Support (#1073)
1 parent 3812fd5 commit 0f93f7b

File tree

22 files changed

+689
-176
lines changed

22 files changed

+689
-176
lines changed

.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ env:
1414
viceroy_version: 0.12.2
1515
# Note: when updated, also update version in ensure-cargo-installs ! AND ! release-please.yml
1616
wasm-tools_version: 1.216.0
17-
fastly-cli_version: 10.13.3
17+
fastly-cli_version: 10.19.0
1818

1919
jobs:
2020
check-changelog:

documentation/docs/acl/Acl/open.mdx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
hide_title: false
3+
hide_table_of_contents: false
4+
pagination_next: null
5+
pagination_prev: null
6+
---
7+
8+
# Acl.open()
9+
10+
Opens the ACL with the given name, returning a new `Acl` instance with the given name on success.
11+
12+
## Syntax
13+
14+
```js
15+
Acl.open(name)
16+
```
17+
18+
### Return value
19+
20+
An `Acl` instance.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
hide_title: false
3+
hide_table_of_contents: false
4+
pagination_next: null
5+
pagination_prev: null
6+
---
7+
8+
# Acl.prototype.lookup()
9+
10+
The **`lookup(ipAddress)`** method returns the name associated with the `Acl` instance.
11+
12+
## Syntax
13+
14+
```js
15+
acl.lookup(ipAddress)
16+
```
17+
18+
### Parameters
19+
20+
- `ipAddress` _: string_
21+
- IPv4 or IPv6 address to lookup
22+
23+
### Return value
24+
25+
An Object of the form `{ action: 'ALLOW' | 'BlOCK', prefix: string }`, where `prefix` is the IP
26+
address prefix that was matched in the ACL.
27+
28+
## Example
29+
30+
```js
31+
/// <reference types="@fastly/js-compute" />
32+
import { Acl } from 'fastly:acl';
33+
addEventListener('fetch', async (evt) => {
34+
const myAcl = Acl.open('myacl');
35+
const { action, prefix } = await myAcl.lookup(evt.client.address);
36+
evt.respondWith(new Response(action === 'BLOCK' ? 'blocked' : 'allowed'));
37+
});
38+
```

integration-tests/js-compute/env.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ export function getEnv(serviceName) {
44
CONFIG_STORE_NAME: `testconfig${serviceName ? '__' + serviceName.replace(/-/g, '_') : ''}`,
55
KV_STORE_NAME: `example-test-kv-store${serviceName ? '--' + serviceName : ''}`,
66
SECRET_STORE_NAME: `example-test-secret-store${serviceName ? '--' + serviceName : ''}`,
7+
ACL_NAME: `exampleacl${serviceName ? '__' + serviceName.replace(/-/g, '_') : ''}`,
78
};
89
}

integration-tests/js-compute/fixtures/app/fastly.toml.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name = "js-test-app"
99
service_id = ""
1010

1111
[scripts]
12-
build = "node ../../../../js-compute-runtime-cli.js --env FASTLY_DEBUG_LOGGING,CONFIG_STORE_NAME,DICTIONARY_NAME,KV_STORE_NAME,SECRET_STORE_NAME,LOCAL_TEST,TEST=\"foo\" --enable-experimental-high-resolution-time-methods src/index.js"
12+
build = "node ../../../../js-compute-runtime-cli.js --env FASTLY_DEBUG_LOGGING,ACL_NAME,CONFIG_STORE_NAME,DICTIONARY_NAME,KV_STORE_NAME,SECRET_STORE_NAME,LOCAL_TEST,TEST=\"foo\" --enable-experimental-high-resolution-time-methods src/index.js"
1313

1414
[local_server]
1515

integration-tests/js-compute/fixtures/module-mode/fastly.toml.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ name = "js-test-app"
99
service_id = ""
1010

1111
[scripts]
12-
build = "node ../../../../js-compute-runtime-cli.js --env FASTLY_DEBUG_LOGGING,CONFIG_STORE_NAME,DICTIONARY_NAME,KV_STORE_NAME,SECRET_STORE_NAME,LOCAL_TEST,TEST=\"foo\" --enable-experimental-high-resolution-time-methods --module-mode src/index.js"
12+
build = "node ../../../../js-compute-runtime-cli.js --env FASTLY_DEBUG_LOGGING,ACL_NAME,CONFIG_STORE_NAME,DICTIONARY_NAME,KV_STORE_NAME,SECRET_STORE_NAME,LOCAL_TEST,TEST=\"foo\" --enable-experimental-high-resolution-time-methods --module-mode src/index.js"
1313

1414
[local_server]
1515

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/// <reference path="../../../../../types/index.d.ts" />
2+
import {
3+
strictEqual,
4+
assertThrows,
5+
assertRejects,
6+
deepStrictEqual,
7+
} from './assertions.js';
8+
import { routes } from './routes.js';
9+
import { env } from 'fastly:env';
10+
import { Acl } from 'fastly:acl';
11+
12+
const ACL_NAME = env('ACL_NAME');
13+
14+
routes.set('/acl', async () => {
15+
assertThrows(
16+
() => Acl(),
17+
TypeError,
18+
"Acl builtin can't be instantiated directly",
19+
);
20+
assertThrows(
21+
() => new Acl(),
22+
TypeError,
23+
"Acl builtin can't be instantiated directly",
24+
);
25+
assertThrows(
26+
() => Acl.open(),
27+
TypeError,
28+
'Acl open: At least 1 argument required, but only 0 passed',
29+
);
30+
assertThrows(() => Acl.open(4), TypeError, 'Acl open: name must be a string');
31+
assertThrows(
32+
() => Acl.open(''),
33+
TypeError,
34+
'Acl open: name can not be an empty string',
35+
);
36+
assertThrows(
37+
() => Acl.open('foo'),
38+
TypeError,
39+
'Acl open: "foo" acl not found',
40+
);
41+
42+
const acl = Acl.open(ACL_NAME);
43+
44+
await assertRejects(
45+
() => acl.lookup(),
46+
TypeError,
47+
'lookup: At least 1 argument required, but only 0 passed',
48+
);
49+
await assertRejects(
50+
() => acl.lookup(5),
51+
Error,
52+
'Invalid address passed to acl.lookup',
53+
);
54+
await assertRejects(
55+
() => acl.lookup('not ip'),
56+
Error,
57+
'Invalid address passed to acl.lookup',
58+
);
59+
await assertRejects(
60+
() => acl.lookup('999.999.999.999'),
61+
Error,
62+
'Invalid address passed to acl.lookup',
63+
);
64+
await assertRejects(
65+
() => acl.lookup('123.123.123'),
66+
Error,
67+
'Invalid address passed to acl.lookup',
68+
);
69+
await assertRejects(
70+
() => acl.lookup('123.123.123.123.123'),
71+
Error,
72+
'Invalid address passed to acl.lookup',
73+
);
74+
await assertRejects(
75+
() => acl.lookup('100.100.0.0/16'),
76+
Error,
77+
'Invalid address passed to acl.lookup',
78+
);
79+
80+
strictEqual(await acl.lookup('123.123.123.123'), null);
81+
strictEqual(await acl.lookup('100.99.100.100'), null);
82+
strictEqual(
83+
await acl.lookup('2a04:4b80:1234:5678:9abc:def0:1234:5678'),
84+
null,
85+
);
86+
87+
deepStrictEqual(await acl.lookup('100.100.100.100'), {
88+
action: 'BLOCK',
89+
prefix: '100.100.0.0/16',
90+
});
91+
deepStrictEqual(await acl.lookup('2a03:4b80::1'), {
92+
action: 'ALLOW',
93+
prefix: '2a03:4b80:0000:0000:0000:0000:0000:0000/32',
94+
});
95+
deepStrictEqual(await acl.lookup('2a03:4b80:5c1d:e8f7:92a3:b45c:61d8:7e9f'), {
96+
action: 'ALLOW',
97+
prefix: '2a03:4b80:0000:0000:0000:0000:0000:0000/32',
98+
});
99+
});

integration-tests/js-compute/fixtures/module-mode/src/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { routes } from './routes.js';
55
import { env } from 'fastly:env';
66
import { enableDebugLogging } from 'fastly:experimental';
77

8+
import './acl.js';
89
import './console.js';
910
import './dynamic-backend.js';
1011
import './hello-world.js';

integration-tests/js-compute/fixtures/module-mode/tests.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"GET /acl": { "environments": ["compute"] },
23
"GET /backend/timeout": {
34
"environments": ["compute"],
45
"downstream_response": {

integration-tests/js-compute/setup.js

+61-51
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@ import { getEnv } from './env.js';
77
const serviceId = argv[2];
88
const serviceName = argv[3];
99

10-
const { DICTIONARY_NAME, CONFIG_STORE_NAME, KV_STORE_NAME, SECRET_STORE_NAME } =
11-
getEnv(serviceName);
10+
const {
11+
DICTIONARY_NAME,
12+
CONFIG_STORE_NAME,
13+
KV_STORE_NAME,
14+
SECRET_STORE_NAME,
15+
ACL_NAME,
16+
} = getEnv(serviceName);
1217

13-
function existingStoreId(stores, existingName) {
18+
function existingListId(stores, existingName) {
1419
const existing = stores.find(
1520
({ Name, name }) => name === existingName || Name === existingName,
1621
);
@@ -34,108 +39,112 @@ if (process.env.FASTLY_API_TOKEN === undefined) {
3439
}
3540
zx.verbose = true;
3641
}
37-
const FASTLY_API_TOKEN = process.env.FASTLY_API_TOKEN;
3842

3943
async function setupConfigStores() {
40-
let stores = await (async function () {
41-
try {
42-
return JSON.parse(
43-
await zx`fastly config-store list --quiet --json --token $FASTLY_API_TOKEN`,
44-
);
45-
} catch {
46-
return [];
47-
}
48-
})();
44+
const stores = JSON.parse(
45+
await zx`fastly config-store list --quiet --json --token $FASTLY_API_TOKEN`,
46+
);
4947

50-
let STORE_ID = existingStoreId(stores, DICTIONARY_NAME);
48+
let STORE_ID = existingListId(stores, DICTIONARY_NAME);
5149
if (!STORE_ID) {
5250
console.log(`Creating new config store ${DICTIONARY_NAME}`);
53-
process.env.STORE_ID = JSON.parse(
51+
STORE_ID = JSON.parse(
5452
await zx`fastly config-store create --quiet --name=${DICTIONARY_NAME} --json --token $FASTLY_API_TOKEN`,
5553
).id;
5654
} else {
5755
console.log(`Using existing config store ${DICTIONARY_NAME}`);
58-
process.env.STORE_ID = STORE_ID;
56+
STORE_ID = STORE_ID;
5957
}
60-
await zx`echo -n 'https://twitter.com/fastly' | fastly config-store-entry update --upsert --key twitter --store-id=$STORE_ID --stdin --token $FASTLY_API_TOKEN`;
58+
await zx`echo -n 'https://twitter.com/fastly' | fastly config-store-entry update --upsert --key twitter --store-id=${STORE_ID} --stdin --token $FASTLY_API_TOKEN`;
6159
try {
62-
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id $STORE_ID --token $FASTLY_API_TOKEN --autoclone`;
60+
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id ${STORE_ID} --token $FASTLY_API_TOKEN --autoclone`;
6361
} catch (e) {
6462
if (!e.message.includes('Duplicate record')) throw e;
6563
}
6664

67-
STORE_ID = existingStoreId(stores, CONFIG_STORE_NAME);
65+
STORE_ID = existingListId(stores, CONFIG_STORE_NAME);
6866
if (!STORE_ID) {
6967
console.log(`Creating new config store ${CONFIG_STORE_NAME}`);
70-
process.env.STORE_ID = JSON.parse(
68+
STORE_ID = JSON.parse(
7169
await zx`fastly config-store create --quiet --name=${CONFIG_STORE_NAME} --json --token $FASTLY_API_TOKEN`,
7270
).id;
7371
} else {
7472
console.log(`Using existing config store ${CONFIG_STORE_NAME}`);
75-
process.env.STORE_ID = STORE_ID;
73+
STORE_ID = STORE_ID;
7674
}
77-
await zx`echo -n 'https://twitter.com/fastly' | fastly config-store-entry update --upsert --key twitter --store-id=$STORE_ID --stdin --token $FASTLY_API_TOKEN`;
75+
await zx`echo -n 'https://twitter.com/fastly' | fastly config-store-entry update --upsert --key twitter --store-id=${STORE_ID} --stdin --token $FASTLY_API_TOKEN`;
7876
try {
79-
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id $STORE_ID --token $FASTLY_API_TOKEN --autoclone`;
77+
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id ${STORE_ID} --token $FASTLY_API_TOKEN --autoclone`;
8078
} catch (e) {
8179
if (!e.message.includes('Duplicate record')) throw e;
8280
}
8381
}
8482

8583
async function setupKVStore() {
86-
let stores = await (async function () {
87-
try {
88-
return JSON.parse(
89-
await zx`fastly kv-store list --quiet --json --token $FASTLY_API_TOKEN`,
90-
).Data;
91-
} catch {
92-
return [];
93-
}
94-
})();
84+
let stores = JSON.parse(
85+
await zx`fastly kv-store list --quiet --json --token $FASTLY_API_TOKEN`,
86+
).Data;
9587

96-
const STORE_ID = existingStoreId(stores, KV_STORE_NAME);
88+
let STORE_ID = existingListId(stores, KV_STORE_NAME);
9789
if (!STORE_ID) {
9890
console.log(`Creating new KV store ${KV_STORE_NAME}`);
99-
process.env.STORE_ID = JSON.parse(
91+
STORE_ID = JSON.parse(
10092
await zx`fastly kv-store create --quiet --name=${KV_STORE_NAME} --json --token $FASTLY_API_TOKEN`,
10193
).StoreID;
10294
} else {
10395
console.log(`Using existing KV store ${KV_STORE_NAME}`);
104-
process.env.STORE_ID = STORE_ID;
96+
STORE_ID = STORE_ID;
10597
}
10698
try {
107-
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id $STORE_ID --token $FASTLY_API_TOKEN --autoclone`;
99+
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id ${STORE_ID} --token $FASTLY_API_TOKEN --autoclone`;
108100
} catch (e) {
109101
if (!e.message.includes('Duplicate record')) throw e;
110102
}
111103
}
112104

113105
async function setupSecretStore() {
114-
let stores = await (async function () {
115-
try {
116-
return JSON.parse(
117-
await zx`fastly secret-store list --quiet --json --token $FASTLY_API_TOKEN`,
118-
);
119-
} catch (e) {
120-
throw e;
121-
}
122-
})();
123-
const STORE_ID = existingStoreId(stores, SECRET_STORE_NAME);
106+
const stores = JSON.parse(
107+
await zx`fastly secret-store list --quiet --json --token $FASTLY_API_TOKEN`,
108+
);
109+
let STORE_ID = existingListId(stores, SECRET_STORE_NAME);
124110
if (!STORE_ID) {
125111
console.log(`Creating new secret store ${SECRET_STORE_NAME}`);
126-
process.env.STORE_ID = JSON.parse(
112+
STORE_ID = JSON.parse(
127113
await zx`fastly secret-store create --quiet --name=${SECRET_STORE_NAME} --json --token $FASTLY_API_TOKEN`,
128114
).id;
129115
} else {
130116
console.log(`Using existing secret store ${SECRET_STORE_NAME}`);
131-
process.env.STORE_ID = STORE_ID;
132117
}
133-
await zx`echo -n 'This is also some secret data' | fastly secret-store-entry create --recreate-allow --name first --store-id=$STORE_ID --stdin --token $FASTLY_API_TOKEN`;
118+
await zx`echo -n 'This is also some secret data' | fastly secret-store-entry create --recreate-allow --name first --store-id=${STORE_ID} --stdin --token $FASTLY_API_TOKEN`;
134119
let key =
135120
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
136-
await zx`echo -n 'This is some secret data' | fastly secret-store-entry create --recreate-allow --name ${key} --store-id=$STORE_ID --stdin --token $FASTLY_API_TOKEN`;
121+
await zx`echo -n 'This is some secret data' | fastly secret-store-entry create --recreate-allow --name ${key} --store-id=${STORE_ID} --stdin --token $FASTLY_API_TOKEN`;
122+
try {
123+
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id ${STORE_ID} --token $FASTLY_API_TOKEN --autoclone`;
124+
} catch (e) {
125+
if (!e.message.includes('Duplicate record')) throw e;
126+
}
127+
}
128+
129+
async function setupAcl() {
130+
let ACL_ID = existingListId(
131+
JSON.parse(
132+
await zx`fastly compute acl list-acls --quiet --json --token $FASTLY_API_TOKEN`,
133+
).data,
134+
ACL_NAME,
135+
);
136+
if (!ACL_ID) {
137+
console.log(`Creating ACL ${ACL_NAME}`);
138+
ACL_ID = JSON.parse(
139+
await zx`fastly compute acl create --name=${ACL_NAME} --token $FASTLY_API_TOKEN --json`,
140+
).id;
141+
await zx`fastly compute acl update --acl-id=${ACL_ID} --operation=create --prefix=100.100.0.0/16 --action=BLOCK --token $FASTLY_API_TOKEN`;
142+
await zx`fastly compute acl update --acl-id=${ACL_ID} --operation=create --prefix=2a03:4b80::/32 --action=ALLOW --token $FASTLY_API_TOKEN`;
143+
} else {
144+
console.log(`Using existing ACL ${ACL_NAME}`);
145+
}
137146
try {
138-
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id $STORE_ID --token $FASTLY_API_TOKEN --autoclone`;
147+
await zx`fastly resource-link create --service-id ${serviceId} --version latest --resource-id ${ACL_ID} --token $FASTLY_API_TOKEN --autoclone`;
139148
} catch (e) {
140149
if (!e.message.includes('Duplicate record')) throw e;
141150
}
@@ -144,5 +153,6 @@ async function setupSecretStore() {
144153
await setupConfigStores();
145154
await setupKVStore();
146155
await setupSecretStore();
156+
await setupAcl();
147157

148158
await zx`fastly service-version activate --service-id ${serviceId} --version latest --token $FASTLY_API_TOKEN`;

0 commit comments

Comments
 (0)