Skip to content

Commit 30c91c8

Browse files
authored
feat(storage): add IpFilter support to bucket metadata (#8623)
1 parent c5fea74 commit 30c91c8

5 files changed

Lines changed: 375 additions & 1 deletion

File tree

handwritten/storage/src/bucket.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,47 @@ export interface EncryptionEnforcementConfig {
308308
restrictionMode?: 'NotRestricted' | 'FullyRestricted';
309309
readonly effectiveTime?: string;
310310
}
311+
312+
/**
313+
* Configuration for a bucket's IP Filter.
314+
*
315+
* @example
316+
* ```
317+
* const {Storage} = require('@google-cloud/storage');
318+
* const storage = new Storage();
319+
* const bucket = storage.bucket('my-bucket');
320+
*
321+
* const metadata = {
322+
* ipFilter: {
323+
* mode: 'Enabled',
324+
* publicNetworkSource: {
325+
* allowedIpCidrRanges: ['192.168.1.1/32']
326+
* }
327+
* }
328+
* };
329+
*
330+
* bucket.setMetadata(metadata, (err, apiResponse) => {
331+
* if (err) {
332+
* console.error(err);
333+
* } else {
334+
* console.log('IP filter updated successfully.');
335+
* }
336+
* });
337+
* ```
338+
*/
339+
export interface IpFilter {
340+
mode?: 'Enabled' | 'Disabled';
341+
publicNetworkSource?: {
342+
allowedIpCidrRanges?: string[];
343+
};
344+
vpcNetworkSources?: {
345+
network?: string;
346+
allowedIpCidrRanges?: string[];
347+
}[];
348+
allowAllServiceAgentAccess?: boolean;
349+
allowCrossOrgVpcs?: boolean;
350+
}
351+
311352
export interface BucketMetadata extends BaseMetadata {
312353
acl?: AclMetadata[] | null;
313354
autoclass?: {
@@ -341,6 +382,7 @@ export interface BucketMetadata extends BaseMetadata {
341382
lockedTime?: string;
342383
};
343384
};
385+
ipFilter?: IpFilter | null;
344386
labels?: {
345387
[key: string]: string | null;
346388
};

handwritten/storage/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ export {
146146
GetNotificationsCallback,
147147
GetNotificationsOptions,
148148
GetNotificationsResponse,
149+
IpFilter,
149150
Labels,
150151
LifecycleAction,
151152
LifecycleCondition,

handwritten/storage/system-test/storage.ts

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ interface ErrorCallbackFunction {
4444
}
4545
import {PubSub, Subscription, Topic} from '@google-cloud/pubsub';
4646
import {getDirName} from '../src/util.js';
47+
import {BucketMetadata} from '../src/bucket.js';
4748

4849
class HTTPError extends Error {
4950
code: number;
@@ -1294,6 +1295,101 @@ describe('storage', function () {
12941295
});
12951296

12961297
describe('bucket metadata', () => {
1298+
describe('ipFilter', () => {
1299+
let ipFilterBucket: Bucket;
1300+
1301+
before(async () => {
1302+
ipFilterBucket = storage.bucket(generateName());
1303+
await ipFilterBucket.create();
1304+
});
1305+
1306+
after(async () => {
1307+
await ipFilterBucket.delete().catch(() => { });
1308+
});
1309+
1310+
1311+
it('should create a bucket with ipFilter', async () => {
1312+
const metadata: BucketMetadata = {
1313+
ipFilter: {
1314+
mode: 'Disabled',
1315+
publicNetworkSource: {
1316+
allowedIpCidrRanges: ['0.0.0.0/0'],
1317+
},
1318+
allowAllServiceAgentAccess: true,
1319+
},
1320+
};
1321+
const bucketToCreate = storage.bucket(generateName());
1322+
const [bucket, apiResponse] = await bucketToCreate.create(metadata);
1323+
1324+
assert.strictEqual(apiResponse.ipFilter?.mode, metadata.ipFilter?.mode);
1325+
assert.deepStrictEqual(apiResponse.ipFilter?.publicNetworkSource?.allowedIpCidrRanges, metadata.ipFilter?.publicNetworkSource?.allowedIpCidrRanges);
1326+
assert.strictEqual(apiResponse.ipFilter?.allowAllServiceAgentAccess, metadata.ipFilter?.allowAllServiceAgentAccess);
1327+
1328+
await bucket.delete().catch(() => {});
1329+
});
1330+
1331+
it('should set ipFilter', async () => {
1332+
const metadata: BucketMetadata = {
1333+
ipFilter: {
1334+
mode: 'Disabled',
1335+
publicNetworkSource: {
1336+
allowedIpCidrRanges: ['0.0.0.0/0', '::/0'],
1337+
},
1338+
allowAllServiceAgentAccess: false,
1339+
},
1340+
};
1341+
const [meta] = await ipFilterBucket.setMetadata(metadata);
1342+
assert.deepStrictEqual(meta.ipFilter, metadata.ipFilter);
1343+
});
1344+
1345+
it('should get ipFilter', async () => {
1346+
const [meta] = await ipFilterBucket.getMetadata();
1347+
assert.strictEqual(meta.ipFilter?.mode, 'Disabled');
1348+
assert.deepStrictEqual(
1349+
meta.ipFilter?.publicNetworkSource?.allowedIpCidrRanges,
1350+
['0.0.0.0/0', '::/0']
1351+
);
1352+
});
1353+
1354+
it('should update ipFilter', async () => {
1355+
const metadata: BucketMetadata = {
1356+
ipFilter: {
1357+
mode: 'Disabled',
1358+
publicNetworkSource: {
1359+
allowedIpCidrRanges: ['203.0.113.0/24'],
1360+
},
1361+
allowAllServiceAgentAccess: false,
1362+
},
1363+
};
1364+
const [meta] = await ipFilterBucket.setMetadata(metadata);
1365+
assert.deepStrictEqual(meta.ipFilter, metadata.ipFilter);
1366+
});
1367+
1368+
it('should clear allowedIpCidrRanges', async () => {
1369+
const [getMeta] = await ipFilterBucket.getMetadata();
1370+
assert.strictEqual(getMeta.ipFilter?.mode, 'Disabled');
1371+
assert.deepStrictEqual(
1372+
getMeta.ipFilter?.publicNetworkSource?.allowedIpCidrRanges,
1373+
['203.0.113.0/24']
1374+
);
1375+
1376+
const metadata: BucketMetadata = {
1377+
ipFilter: {
1378+
mode: 'Disabled',
1379+
publicNetworkSource: {
1380+
// The API omits the field when the array is cleared.
1381+
allowedIpCidrRanges: [],
1382+
},
1383+
allowAllServiceAgentAccess: false,
1384+
},
1385+
};
1386+
const [meta] = await ipFilterBucket.setMetadata(metadata);
1387+
assert.strictEqual(meta.ipFilter?.mode, 'Disabled');
1388+
assert.strictEqual(meta.ipFilter?.publicNetworkSource?.allowedIpCidrRanges, undefined);
1389+
assert.strictEqual(meta.ipFilter?.allowAllServiceAgentAccess, false);
1390+
});
1391+
});
1392+
12971393
it('should allow setting metadata on a bucket', async () => {
12981394
const metadata = {
12991395
website: {
@@ -4608,7 +4704,15 @@ describe('storage', function () {
46084704
setTimeout(resolve, RETENTION_DURATION_SECONDS * 1000),
46094705
);
46104706
return Promise.all(
4611-
buckets.map(bucket => limit(() => deleteBucketAsync(bucket))),
4707+
buckets.map(bucket =>
4708+
limit(() =>
4709+
deleteBucketAsync(bucket).catch((err: ApiError) => {
4710+
if (err.code !== 404) {
4711+
throw err;
4712+
}
4713+
})
4714+
)
4715+
)
46124716
);
46134717
}
46144718

handwritten/storage/test/bucket.ts

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3714,5 +3714,185 @@ describe('Bucket', () => {
37143714
bucket.setMetadata(clearMetadata, assert.ifError);
37153715
});
37163716
});
3717+
3718+
describe('ipFilter', () => {
3719+
it('should pass ipFilter to create', done => {
3720+
const metadata = {
3721+
ipFilter: {
3722+
mode: 'Disabled',
3723+
publicNetworkSource: {
3724+
allowedIpCidrRanges: ['192.168.0.0/16'],
3725+
},
3726+
allowAllServiceAgentAccess: true,
3727+
},
3728+
};
3729+
3730+
const storageMock = Object.assign({}, bucket.storage, {
3731+
createBucket: (
3732+
name: string,
3733+
options: unknown,
3734+
callback: Function
3735+
) => {
3736+
assert.strictEqual(name, bucket.name);
3737+
assert.deepStrictEqual(options, metadata);
3738+
callback(null, bucket, metadata);
3739+
},
3740+
});
3741+
3742+
const testBucket = new Bucket(storageMock, bucket.name);
3743+
testBucket.create(metadata, (err: Error | null) => {
3744+
assert.ifError(err);
3745+
done();
3746+
});
3747+
});
3748+
3749+
it('should enable ipFilter', done => {
3750+
const metadata = {
3751+
ipFilter: {
3752+
mode: 'Enabled',
3753+
publicNetworkSource: {
3754+
allowedIpCidrRanges: ['192.168.1.1/32'],
3755+
},
3756+
},
3757+
};
3758+
3759+
bucket.parent.request = (
3760+
reqOpts: DecorateRequestOptions,
3761+
callback: Function
3762+
) => {
3763+
assert.strictEqual(reqOpts.method, 'PATCH');
3764+
assert.deepStrictEqual(reqOpts.json.ipFilter, metadata.ipFilter);
3765+
callback(null, metadata);
3766+
};
3767+
3768+
bucket.setMetadata(metadata, (err: Error | null) => {
3769+
assert.ifError(err);
3770+
done();
3771+
});
3772+
});
3773+
3774+
it('should update ipFilter', done => {
3775+
const metadata = {
3776+
ipFilter: {
3777+
mode: 'Enabled',
3778+
vpcNetworkSources: [
3779+
{
3780+
network: 'projects/my-project/global/networks/my-vpc',
3781+
allowedIpCidrRanges: ['10.0.0.0/8'],
3782+
},
3783+
],
3784+
},
3785+
};
3786+
3787+
bucket.parent.request = (
3788+
reqOpts: DecorateRequestOptions,
3789+
callback: Function
3790+
) => {
3791+
assert.strictEqual(reqOpts.method, 'PATCH');
3792+
assert.deepStrictEqual(reqOpts.json.ipFilter, metadata.ipFilter);
3793+
callback(null, metadata);
3794+
};
3795+
3796+
bucket.setMetadata(metadata, (err: Error | null) => {
3797+
assert.ifError(err);
3798+
done();
3799+
});
3800+
});
3801+
3802+
it('should get ipFilter', async () => {
3803+
const ipFilter = {
3804+
mode: 'Enabled',
3805+
publicNetworkSource: {
3806+
allowedIpCidrRanges: ['192.168.1.1/32'],
3807+
},
3808+
vpcNetworkSources: [
3809+
{
3810+
network: 'projects/my-project/global/networks/my-vpc',
3811+
allowedIpCidrRanges: ['10.0.0.0/8'],
3812+
},
3813+
],
3814+
allowAllServiceAgentAccess: true,
3815+
allowCrossOrgVpcs: true,
3816+
};
3817+
3818+
bucket.parent.request = (
3819+
reqOpts: DecorateRequestOptions,
3820+
callback: Function
3821+
) => {
3822+
callback(null, {ipFilter});
3823+
};
3824+
3825+
const [metadata] = (await bucket.getMetadata()) as [BucketMetadata];
3826+
assert.deepStrictEqual(metadata.ipFilter, ipFilter);
3827+
});
3828+
3829+
it('should clear allowedIpCidrRanges', done => {
3830+
const initialIpFilter = {
3831+
mode: 'Disabled',
3832+
publicNetworkSource: {
3833+
allowedIpCidrRanges: ['203.0.113.0/24'],
3834+
},
3835+
};
3836+
3837+
const updatedIpFilter = {
3838+
mode: 'Disabled',
3839+
publicNetworkSource: {
3840+
allowedIpCidrRanges: undefined,
3841+
},
3842+
allowAllServiceAgentAccess: false,
3843+
};
3844+
3845+
bucket.parent.request = (
3846+
reqOpts: DecorateRequestOptions,
3847+
callback: Function
3848+
) => {
3849+
if (reqOpts.method === 'PATCH') {
3850+
assert.deepStrictEqual(
3851+
reqOpts.json.ipFilter?.publicNetworkSource?.allowedIpCidrRanges,
3852+
[]
3853+
);
3854+
callback(null, {ipFilter: updatedIpFilter});
3855+
} else {
3856+
callback(null, {ipFilter: initialIpFilter});
3857+
}
3858+
};
3859+
3860+
bucket.getMetadata((err: Error | null, getMeta?: BucketMetadata) => {
3861+
assert.ifError(err);
3862+
assert.strictEqual(getMeta?.ipFilter?.mode, 'Disabled');
3863+
assert.deepStrictEqual(
3864+
getMeta?.ipFilter?.publicNetworkSource?.allowedIpCidrRanges,
3865+
['203.0.113.0/24']
3866+
);
3867+
3868+
const metadataUpdate = {
3869+
ipFilter: {
3870+
mode: 'Disabled',
3871+
publicNetworkSource: {
3872+
allowedIpCidrRanges: [],
3873+
},
3874+
allowAllServiceAgentAccess: false,
3875+
},
3876+
};
3877+
3878+
bucket.setMetadata(
3879+
metadataUpdate,
3880+
(err: Error | null, meta?: BucketMetadata) => {
3881+
assert.ifError(err);
3882+
assert.strictEqual(meta?.ipFilter?.mode, 'Disabled');
3883+
assert.strictEqual(
3884+
meta?.ipFilter?.publicNetworkSource?.allowedIpCidrRanges,
3885+
undefined
3886+
);
3887+
assert.strictEqual(
3888+
meta?.ipFilter?.allowAllServiceAgentAccess,
3889+
false
3890+
);
3891+
done();
3892+
}
3893+
);
3894+
});
3895+
});
3896+
});
37173897
});
37183898
});

0 commit comments

Comments
 (0)