Skip to content

Conversation

inlined
Copy link
Member

@inlined inlined commented Mar 10, 2025

The emulator can now read secret references at startup. Care is taken to be fully reverse compatible so that projectId is only needed when loading a secret value from an id. Cloud Secrets Manager is only ever called if the customer tries to reference the secret.

Future changes may evolve the best practice to move customers to using a secret reference, but must steer customers (hard) away from accidentally committing a plaintext secret to their repository.

@inlined inlined requested review from mathu97 and taeold March 11, 2025 19:16
@taeold taeold requested a review from annajowang March 12, 2025 21:49
Comment on lines 48 to 50
const secretResourceRegex =
/^projects\/([^/]+)\/secrets\/([^/]+)(?:\/versions\/((?:latest)|\d+))?$/;
const secretShorthandRegex = /^([^/@]+)(?:@((?:latest)|\d+))?$/;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can there be comments that show examples of what these match


await serve.start({ startCommand });

expect(spawnWithCommandStringStub).to.be.called;
expect(spawnWithCommandStringStub.getCall(0).args[0]).to.eq(startCommand);
});

it("Should pass plaintext environment varialbes", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"varialbes"

@@ -55,13 +90,17 @@ async function serve(
for (const env of apphostingLocalConfig.environmentVariables) {
environmentVariablesAsRecord[env.variable] = env.value!;
}
await Promise.all(
apphostingLocalConfig.secrets?.map(async (secret) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should update the logic for merge:

for (const [key, value] of other._secrets) {

Where we remove secrets if they are overridden by an env var in apphosting.local.yaml

Otherwise, there is no way to override the secret to prevent it from fetching it during emulator start.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm just going to remove the shorthand of variables vs secrets. The helper methods are a lot of work just to avoid a filter on a call site and as you point out, make merging fragile

Copy link
Member Author

@inlined inlined left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on Daniel's catch that env wasn't merging right, I did the cleanup that I was delaying for another CL.

IDK why the appyhosting yaml file was made to look like idiomatic java instead of idiomatic javascript. Were we afraid to use a little functional programming? Ripping all that out fixed the bug, made future code less fragile, and removed about 300LOC.

availability: ["RUNTIME"],
});
};
}

// remove secrets to avoid confusion as they are not read anyways.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can delete this comment

yamlConfigToWrite.env = [...this.environmentVariables, ...this.secrets];
yamlConfigToWrite.env = Object.entries(this.env).map(([variable, env]) => {
return { variable, ...env };
});

store(filePath, yaml.parseDocument(jsYaml.dump(yamlConfigToWrite)));
}
}

function parseEnv(envs: Env[]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type is Record<string, Env> i think?

.map(([key]) => key);
if (wereSecrets.some((key) => other.env[key]?.value)) {
throw new FirebaseError(
`Cannot convert secret to plaintext in ${other.filename ? other.filename : "apphosting yaml"}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`Cannot convert secret to plaintext in ${other.filename ? other.filename : "apphosting yaml"}`,
`Cannot convert secret to plaintext in ${other.filename ? other.filename : "apphosting.yaml"}`,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively, other.filename ?? "apphosting.yaml"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shoudl only ever happen in tests where we start with .empty() instead of loading from a file. I wanted something different so that we didn't accidentally start printing invalid file names if we didn't actually know what was right.

secretId = match[1];
version = match[2] || "latest";
}
return await secrets.accessSecretVersion(projectId, secretId, version);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we try/catch this to print user friendly log when status == 401 with suggestion to use firebase apphosting:secrets:grantaccess command?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding for 403. 401 should never happen, right?

JSON.stringify([{ variable: "TEST", value: "overwritten_value" }]),
);
describe("merge", () => {
it("merges incoming apphosting yaml config with precendence", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it("merges incoming apphosting yaml config with precendence", () => {
it("merges incoming apphosting.yaml config with precendence", () => {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically incorrect, no? It's about merging apphosting.local.yaml over apphosting.emulators.yaml over apphosting.yaml

@inlined inlined merged commit 90b3f17 into master Mar 18, 2025
53 of 55 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants