Skip to content

Commit 2c897ea

Browse files
authored
Add runtime option to associate secrets to functions (#1018)
Google Cloud Functions (GCF) now have support for loading secrets stored in Secret Manager to securely access application secrets (e.g. Third party API keys) on function executions. See the [documentations(https://cloud.google.com/functions/docs/configuring/secrets) for more details. We propose extending the function runtime option to associate secrets. Then, the secrets can be access as an environment once deployed to GCF.
1 parent 8a515c7 commit 2c897ea

8 files changed

+66
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add new runtime option for setting secrets.

spec/v1/cloud-functions.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ describe('makeCloudFunction', () => {
107107
regions: ['us-central1'],
108108
memory: '128MB',
109109
serviceAccount: '[email protected]',
110+
secrets: ['MY_SECRET'],
110111
},
111112
});
112113

@@ -123,6 +124,7 @@ describe('makeCloudFunction', () => {
123124
},
124125
retry: false,
125126
},
127+
secretEnvironmentVariables: [{ secret: 'MY_SECRET', key: 'MY_SECRET' }],
126128
labels: {},
127129
});
128130
});

spec/v1/function-builder.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,11 +472,45 @@ describe('FunctionBuilder', () => {
472472
).to.throw();
473473
});
474474

475-
it('', () => {
475+
it('should throw an error if private identifier is in the invoker array', () => {
476476
expect(() =>
477477
functions.runWith({
478478
invoker: ['service-account1', 'private', 'service-account2'],
479479
})
480480
).to.throw();
481481
});
482+
483+
it('should allow valid secret config expressed using short form', () => {
484+
const secrets = ['API_KEY'];
485+
const fn = functions
486+
.runWith({ secrets })
487+
.auth.user()
488+
.onCreate((user) => user);
489+
490+
expect(fn.__trigger.secrets).to.deep.equal(secrets);
491+
});
492+
493+
it('should throw error given secrets expressed with full resource name', () => {
494+
expect(() =>
495+
functions.runWith({
496+
secrets: ['projects/my-project/secrets/API_KEY'],
497+
})
498+
).to.throw();
499+
});
500+
501+
it('should throw error given invalid secret config', () => {
502+
expect(() =>
503+
functions.runWith({
504+
secrets: ['ABC/efg'],
505+
})
506+
).to.throw();
507+
});
508+
509+
it('should throw error given invalid secret with versions', () => {
510+
expect(() =>
511+
functions.runWith({
512+
secrets: ['ABC@3'],
513+
})
514+
).to.throw();
515+
});
482516
});

src/cloud-functions.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ export interface TriggerAnnotated {
281281
vpcConnectorEgressSettings?: string;
282282
serviceAccountEmail?: string;
283283
ingressSettings?: string;
284+
secrets?: string[];
284285
};
285286
}
286287

@@ -552,7 +553,8 @@ export function optionsToTrigger(options: DeploymentOptions) {
552553
'ingressSettings',
553554
'vpcConnectorEgressSettings',
554555
'vpcConnector',
555-
'labels'
556+
'labels',
557+
'secrets'
556558
);
557559
convertIfPresent(
558560
trigger,
@@ -620,6 +622,13 @@ export function optionsToEndpoint(
620622
'serviceAccount',
621623
(sa) => sa
622624
);
625+
convertIfPresent(
626+
endpoint,
627+
options,
628+
'secretEnvironmentVariables',
629+
'secrets',
630+
(secrets) => secrets.map((secret) => ({ secret, key: secret }))
631+
);
623632
if (options?.vpcConnector) {
624633
endpoint.vpc = { connector: options.vpcConnector };
625634
convertIfPresent(

src/common/manifest.ts

Whitespace-only changes.

src/function-builder.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ function assertRuntimeOptionsValid(runtimeOptions: RuntimeOptions): boolean {
229229
}
230230
}
231231

232+
if (runtimeOptions.secrets !== undefined) {
233+
const invalidSecrets = runtimeOptions.secrets.filter(
234+
(s) => !/^[A-Za-z\d\-_]+$/.test(s)
235+
);
236+
if (invalidSecrets.length > 0) {
237+
throw new Error(
238+
`Invalid secrets: ${invalidSecrets.join(',')}. ` +
239+
'Secret must be configured using the resource id (e.g. API_KEY)'
240+
);
241+
}
242+
}
243+
232244
return true;
233245
}
234246

src/function-configuration.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ export interface RuntimeOptions {
165165
* Allow requests with invalid App Check tokens on callable functions.
166166
*/
167167
allowInvalidAppCheckToken?: boolean;
168+
169+
/*
170+
* Secrets to bind to a function instance.
171+
*/
172+
secrets?: string[];
168173
}
169174

170175
export interface DeploymentOptions extends RuntimeOptions {

src/runtime/manifest.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface ManifestEndpoint {
3939
labels?: Record<string, string>;
4040
ingressSettings?: string;
4141
environmentVariables?: Record<string, string>;
42+
secretEnvironmentVariables?: { key: string; secret?: string }[];
4243

4344
httpsTrigger?: {
4445
invoker?: string[];

0 commit comments

Comments
 (0)