diff --git a/spec/v1/function-builder.spec.ts b/spec/v1/function-builder.spec.ts index bfb9e99fc..86a441c67 100644 --- a/spec/v1/function-builder.spec.ts +++ b/spec/v1/function-builder.spec.ts @@ -439,4 +439,44 @@ describe('FunctionBuilder', () => { }) ).to.throw(); }); + + it('should throw an error if invoker is an empty string', () => { + expect(() => + functions.runWith({ + invoker: '', + }) + ).to.throw(); + }); + + it('should throw an error if invoker is an empty array', () => { + expect(() => + functions.runWith({ + invoker: [''], + }) + ).to.throw(); + }); + + it('should throw an error if invoker has an empty string', () => { + expect(() => + functions.runWith({ + invoker: ['service-account1', '', 'service-account2'], + }) + ).to.throw(); + }); + + it('should throw an error if public identifier is in the invoker array', () => { + expect(() => + functions.runWith({ + invoker: ['service-account1', 'public', 'service-account2'], + }) + ).to.throw(); + }); + + it('', () => { + expect(() => + functions.runWith({ + invoker: ['service-account1', 'private', 'service-account2'], + }) + ).to.throw(); + }); }); diff --git a/src/v1/cloud-functions.ts b/src/v1/cloud-functions.ts index ef7b8debe..fc8f1406a 100644 --- a/src/v1/cloud-functions.ts +++ b/src/v1/cloud-functions.ts @@ -27,6 +27,7 @@ import { DEFAULT_FAILURE_POLICY, DeploymentOptions, FailurePolicy, + Invoker, Schedule, } from './function-configuration'; export { Request, Response }; @@ -278,6 +279,7 @@ export interface TriggerAnnotated { vpcConnectorEgressSettings?: string; serviceAccountEmail?: string; ingressSettings?: string; + invoker?: Invoker | Invoker[]; }; } @@ -497,7 +499,8 @@ export function optionsToTrigger(options: DeploymentOptions) { 'ingressSettings', 'vpcConnectorEgressSettings', 'vpcConnector', - 'labels' + 'labels', + 'invoker' ); convertIfPresent( trigger, diff --git a/src/v1/function-builder.ts b/src/v1/function-builder.ts index b421668d4..ada3b724c 100644 --- a/src/v1/function-builder.ts +++ b/src/v1/function-builder.ts @@ -192,6 +192,43 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean { ); } } + + if ( + typeof runtimeOptions.invoker === 'string' && + runtimeOptions.invoker.length === 0 + ) { + throw new Error( + 'Invalid service account for function invoker, must be a non-empty string' + ); + } + if ( + runtimeOptions.invoker !== undefined && + Array.isArray(runtimeOptions.invoker) + ) { + if (runtimeOptions.invoker.length === 0) { + throw new Error( + 'Invalid invoker array, must contain at least 1 service account entry' + ); + } + for (const serviceAccount of runtimeOptions.invoker) { + if (serviceAccount.length === 0) { + throw new Error( + 'Invalid invoker array, a service account must be a non-empty string' + ); + } + if (serviceAccount === 'public') { + throw new Error( + "Invalid invoker array, a service account cannot be set to the 'public' identifier" + ); + } + if (serviceAccount === 'private') { + throw new Error( + "Invalid invoker array, a service account cannot be set to the 'private' identifier" + ); + } + } + } + return true; } diff --git a/src/v1/function-configuration.ts b/src/v1/function-configuration.ts index b1ed51623..f7acef18e 100644 --- a/src/v1/function-configuration.ts +++ b/src/v1/function-configuration.ts @@ -98,6 +98,11 @@ export const DEFAULT_FAILURE_POLICY: FailurePolicy = { export const MAX_NUMBER_USER_LABELS = 58; +/** + * Invoker access control type for https functions. + */ +export type Invoker = 'public' | 'private' | string; + export interface RuntimeOptions { /** * Which platform should host the backend. Valid options are "gcfv1" @@ -156,6 +161,11 @@ export interface RuntimeOptions { * User labels to set on the function. */ labels?: Record; + + /** + * Invoker to set access control on https functions. + */ + invoker?: Invoker | Invoker[]; } export interface DeploymentOptions extends RuntimeOptions {