Skip to content

Commit 14c6bec

Browse files
committed
use standardized uri format
1 parent 23b799c commit 14c6bec

7 files changed

Lines changed: 90 additions & 110 deletions

File tree

src/deploy/functions/build.spec.ts

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ describe("envWithType", () => {
390390
});
391391

392392
describe("applyEnvSecretOverrides", () => {
393-
it("should add the default secret for the keyname to secretEnvironmentVariables if not present", () => {
393+
it("throws an error if the secret is in an unknown format", () => {
394394
const testBuild: build.Build = {
395395
endpoints: {
396396
func: {
@@ -405,15 +405,33 @@ describe("applyEnvSecretOverrides", () => {
405405
params: [],
406406
requiredAPIs: [],
407407
};
408-
const testSecretRefs: Record<string, string> = { foo: "" };
409-
build.applyEnvSecretOverrides(testBuild, testSecretRefs);
410-
expect(testBuild.endpoints["func"].secretEnvironmentVariables).to.deep.equal([
411-
{
412-
key: "foo",
413-
secret: "foo",
414-
projectId: "test-project",
408+
const testSecretRefs: Record<string, string> = {
409+
foo: "projects/1234/secrets/api_key/versions/2",
410+
};
411+
expect(() => build.applyEnvSecretOverrides(testBuild, testSecretRefs)).to.throw(
412+
/not a valid Cloud Secret Manager reference/,
413+
);
414+
});
415+
416+
it("throws an error if the secret references an invalid Cloud resource id", () => {
417+
const testBuild: build.Build = {
418+
endpoints: {
419+
func: {
420+
region: "us-central1",
421+
project: "test-project",
422+
platform: "gcfv2",
423+
runtime: "nodejs18",
424+
entryPoint: "func1",
425+
httpsTrigger: {},
426+
},
415427
},
416-
]);
428+
params: [],
429+
requiredAPIs: [],
430+
};
431+
const testSecretRefs: Record<string, string> = { foo: "csm://NOCAPS/4" };
432+
expect(() => build.applyEnvSecretOverrides(testBuild, testSecretRefs)).to.throw(
433+
/not a valid Cloud Secret Manager reference/,
434+
);
417435
});
418436

419437
it("should add the referenced secret to secretEnvironmentVariables if not present", () => {
@@ -431,7 +449,7 @@ describe("applyEnvSecretOverrides", () => {
431449
params: [],
432450
requiredAPIs: [],
433451
};
434-
const testSecretRefs: Record<string, string> = { foo: "bar" };
452+
const testSecretRefs: Record<string, string> = { foo: "csm://bar" };
435453
build.applyEnvSecretOverrides(testBuild, testSecretRefs);
436454
expect(testBuild.endpoints["func"].secretEnvironmentVariables).to.deep.equal([
437455
{
@@ -457,7 +475,7 @@ describe("applyEnvSecretOverrides", () => {
457475
params: [],
458476
requiredAPIs: [],
459477
};
460-
const testSecretRefs: Record<string, string> = { foo: "bar@4" };
478+
const testSecretRefs: Record<string, string> = { foo: "csm://bar/4" };
461479
build.applyEnvSecretOverrides(testBuild, testSecretRefs);
462480
expect(testBuild.endpoints["func"].secretEnvironmentVariables).to.deep.equal([
463481
{
@@ -484,7 +502,7 @@ describe("applyEnvSecretOverrides", () => {
484502
params: [],
485503
requiredAPIs: [],
486504
};
487-
const testSecretRefs: Record<string, string> = { foo: "bar@latest" };
505+
const testSecretRefs: Record<string, string> = { foo: "csm://bar/latest" };
488506
build.applyEnvSecretOverrides(testBuild, testSecretRefs);
489507
expect(testBuild.endpoints["func"].secretEnvironmentVariables).to.deep.equal([
490508
{
@@ -518,7 +536,7 @@ describe("applyEnvSecretOverrides", () => {
518536
params: [],
519537
requiredAPIs: [],
520538
};
521-
const testSecretRefs: Record<string, string> = { foo: "bar@4" };
539+
const testSecretRefs: Record<string, string> = { foo: "csm://bar/4" };
522540
build.applyEnvSecretOverrides(testBuild, testSecretRefs);
523541
expect(testBuild.endpoints["func"].secretEnvironmentVariables).to.deep.equal([
524542
{

src/deploy/functions/build.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ import { ExprParseError } from "./cel";
99
import { defineSecret } from "firebase-functions/params";
1010

1111
export const REGION_TBD = "REGION_TBD";
12+
export const SECRET_REF_PREFIX = "FIREBASE_SECRET_REF_";
13+
// prettier-ignore
14+
export const SECRET_REF_RE = new RegExp(
15+
"^" + // start of string
16+
"csm:\/\/" + // scheme csm://
17+
"([a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)" + // secret ID is a GCP resource ID
18+
"(?:\/([-a-z0-9]+))?" + // capture an optional GCP label for version
19+
"$" // end of string
20+
);
1221

1322
/* The union of a customer-controlled deployment and potentially deploy-time defined parameters */
1423
export interface Build {
@@ -737,15 +746,23 @@ export function applyPrefix(build: Build, prefix: string): void {
737746

738747
/**
739748
* Applies overrides from the .env file binding Secrets to a different Cloud Secret Manager resource.
740-
* For example, "&API_KEY=other@2" binds the value of /projects/1234/secrets/other/versions/2 to env var API_KEY and the secret param named API_KEY.
749+
* Secrets references are of the form csm://secretName/version, referencing a Secret in the same project as the Endpoint.
750+
* /version can be omitted and will cause the secret to resolve to whatever the latest version was at time of deploy.
741751
*
742752
* For each binding imported from the .env file,
743753
* 1) TODO: Check if a conflicting SecretParam with the same name exists. If so, override the param so that the prompting flow will look in the right place when deciding whether or not to create a new Secret.
744754
* 2) Upsert the binding directly into the Build's SecretEnvVars, which will cause it to be actually available in process.ENV
745755
*/
746756
export function applyEnvSecretOverrides(build: Build, envSecrets: Record<string, string>) {
747757
Object.entries(envSecrets).forEach(([key, secretRef]) => {
748-
const [resourceId, versionRef] = secretRef.split("@");
758+
const match = SECRET_REF_RE.exec(secretRef);
759+
if (!match) {
760+
throw new FirebaseError(
761+
`Secret reference '${secretRef}' is not a valid Cloud Secret Manager reference.`,
762+
);
763+
}
764+
const resourceId = match[1];
765+
const versionRef = match[2];
749766

750767
Object.entries(build.endpoints).forEach(([, endpoint]) => {
751768
let needEnvInsert = true;

src/deploy/functions/prepare.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import { BEFORE_CREATE_EVENT, BEFORE_SIGN_IN_EVENT } from "../../functions/event
2121
import { latest } from "./runtimes/supported";
2222

2323
describe("partition env helper", () => {
24-
it("splits a Record into two based on which keys begin with &", () => {
24+
it("splits a Record into two based on which keys begin with FIREBASE_SECRET_REF", () => {
2525
const input = {
2626
foo: "bar",
27-
"&baz": "quux",
27+
FIREBASE_SECRET_REF_baz: "quux",
2828
} as Record<string, string>;
2929
const { userEnvs: userEnvs, secretRefs: secretRefs } = prepare.partitionUserEnvs(input);
3030
expect(userEnvs).to.deep.equal({ foo: "bar" });

src/deploy/functions/prepare.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export async function prepare(
152152
.filter((e) => e.codebase === codebase || e.codebase === undefined);
153153
await resolveDefaultRegionsForBuild(wantBuild, backend.of(...relevantEndpoints));
154154

155-
if (experiments.isEnabled("functionsEnvParams")) {
155+
if (experiments.isEnabled("secretEnvParams")) {
156156
build.applyEnvSecretOverrides(wantBuild, secretRefs);
157157
}
158158

@@ -798,16 +798,18 @@ export async function ensureAllRequiredAPIsEnabled(
798798
}
799799

800800
/**
801-
* Divides the environment variables between normal envs and references to a Secret resource
801+
* Divides the environment variables between normal envs and references to a Secret resource.
802+
* Secret references begin with FIREBASE_SECRET_REF_ and are not provided directly in process.env,
803+
* but the Cloud Secret Manager resource they reference is loaded through SecretEnvVars.
802804
*/
803805
export function partitionUserEnvs(allEnvs: Record<string, string>): {
804806
userEnvs: Record<string, string>;
805807
secretRefs: Record<string, string>;
806808
} {
807809
const [secretRefs, userEnvs] = Object.entries(allEnvs).reduce(
808810
(accumulatedSplit, [k, v]) => {
809-
if (k.startsWith("&")) {
810-
accumulatedSplit[0][k.slice(1)] = v;
811+
if (k.startsWith(build.SECRET_REF_PREFIX)) {
812+
accumulatedSplit[0][k.replace(new RegExp("^" + build.SECRET_REF_PREFIX), "")] = v;
811813
} else {
812814
accumulatedSplit[1][k] = v;
813815
}

src/experiments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export const ALL_EXPERIMENTS = experiments({
214214
default: false,
215215
public: true,
216216
},
217-
functionsEnvParams: {
217+
secretEnvParams: {
218218
shortDescription:
219219
"Enable experimental support for storing references to Secrets in the .env for a Functions deploy.",
220220
default: false,

src/functions/env.spec.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@ describe("functions/env", () => {
1616
input: "FOO=foo ",
1717
want: { FOO: "foo" },
1818
},
19-
{
20-
description: "should parse &key values with trailing spaces",
21-
input: "&FOO=foo ",
22-
want: { "&FOO": "foo" },
23-
},
2419
{
2520
description: "should parse exported values",
2621
input: "export FOO=foo",
@@ -45,15 +40,6 @@ BAR=bar
4540
`,
4641
want: { FOO: "foo1\nfoo2", BAR: "bar" },
4742
},
48-
{
49-
description: "should parse multi-line values without getting confused about &",
50-
input: `
51-
&FOO="foo1
52-
&foo2"
53-
BAR=bar
54-
`,
55-
want: { "&FOO": "foo1\n&foo2", BAR: "bar" },
56-
},
5743
{
5844
description: "should parse many double quoted values",
5945
input: 'FOO="foo"\nBAR="bar"',
@@ -300,6 +286,10 @@ FOO=foo
300286
expect(() => {
301287
env.validateKey("EXT_INSTANCE_ID");
302288
}).to.throw("starts with a reserved prefix");
289+
290+
it("except when the key is an allowlisted form", () => {
291+
env.validateKey("FIREBASE_SECRET_REF_API_KEY");
292+
});
303293
});
304294
});
305295

0 commit comments

Comments
 (0)