Skip to content

Commit eb99545

Browse files
committed
fresh auth token for each deploy
1 parent e16c43c commit eb99545

File tree

3 files changed

+106
-9
lines changed

3 files changed

+106
-9
lines changed

apps/webapp/app/v3/getDeploymentImageRef.server.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type Repository,
66
type Tag,
77
RepositoryNotFoundException,
8+
GetAuthorizationTokenCommand,
89
} from "@aws-sdk/client-ecr";
910
import { tryCatch } from "@trigger.dev/core";
1011
import { logger } from "~/services/logger.server";
@@ -58,7 +59,7 @@ export async function getDeploymentImageRef({
5859
};
5960
}
6061

61-
function isEcrRegistry(registryHost: string) {
62+
export function isEcrRegistry(registryHost: string) {
6263
return registryHost.includes("amazonaws.com");
6364
}
6465

@@ -196,3 +197,39 @@ async function ensureEcrRepositoryExists({
196197

197198
return newRepo;
198199
}
200+
201+
export async function getEcrAuthToken({
202+
registryHost,
203+
registryId,
204+
}: {
205+
registryHost: string;
206+
registryId?: string;
207+
}): Promise<{ username: string; password: string }> {
208+
const region = getEcrRegion(registryHost);
209+
if (!region) {
210+
logger.error("Invalid ECR registry host", { registryHost });
211+
throw new Error("Invalid ECR registry host");
212+
}
213+
214+
const ecr = new ECRClient({ region });
215+
const response = await ecr.send(
216+
new GetAuthorizationTokenCommand({
217+
registryIds: registryId ? [registryId] : undefined,
218+
})
219+
);
220+
221+
if (!response.authorizationData) {
222+
throw new Error("Failed to get ECR authorization token");
223+
}
224+
225+
const authData = response.authorizationData[0];
226+
227+
if (!authData.authorizationToken) {
228+
throw new Error("No authorization token returned from ECR");
229+
}
230+
231+
const authToken = Buffer.from(authData.authorizationToken, "base64").toString();
232+
const [username, password] = authToken.split(":");
233+
234+
return { username, password };
235+
}

apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { env } from "~/env.server";
99
import { depot as execDepot } from "@depot/cli";
1010
import { FinalizeDeploymentService } from "./finalizeDeployment.server";
1111
import { remoteBuildsEnabled } from "../remoteImageBuilder.server";
12+
import { getEcrAuthToken, isEcrRegistry } from "../getDeploymentImageRef.server";
13+
import { tryCatch } from "@trigger.dev/core";
1214

1315
export class FinalizeDeploymentV2Service extends BaseService {
1416
public async call(
@@ -172,11 +174,27 @@ async function executePushToRegistry(
172174
{ depot, registry, deployment }: ExecutePushToRegistryOptions,
173175
writer?: WritableStreamDefaultWriter
174176
): Promise<ExecutePushResult> {
175-
// Step 1: We need to "login" to the digital ocean registry
176-
const configDir = await ensureLoggedIntoDockerRegistry(registry.host, {
177-
username: registry.username,
178-
password: registry.password,
179-
});
177+
// Step 1: We need to "login" to the registry
178+
const [loginError, configDir] = await tryCatch(
179+
ensureLoggedIntoDockerRegistry(registry.host, {
180+
username: registry.username,
181+
password: registry.password,
182+
})
183+
);
184+
185+
if (loginError) {
186+
logger.error("Failed to login to registry", {
187+
deployment,
188+
registryHost: registry.host,
189+
error: loginError.message,
190+
});
191+
192+
return {
193+
ok: false as const,
194+
error: "Failed to login to registry",
195+
logs: "",
196+
};
197+
}
180198

181199
const imageTag = deployment.imageReference;
182200

@@ -244,12 +262,18 @@ async function executePushToRegistry(
244262

245263
async function ensureLoggedIntoDockerRegistry(
246264
registryHost: string,
247-
auth: { username: string; password: string }
265+
auth: { username: string; password: string } | undefined = undefined
248266
) {
249267
const tmpDir = await createTempDir();
250-
// Read the current docker config
251268
const dockerConfigPath = join(tmpDir, "config.json");
252269

270+
// If this is an ECR registry, get fresh credentials
271+
if (isEcrRegistry(registryHost)) {
272+
auth = await getEcrAuthToken({ registryHost, registryId: env.DEPLOY_REGISTRY_ID });
273+
} else if (!auth) {
274+
throw new Error("Authentication required for non-ECR registry");
275+
}
276+
253277
await writeJSONFile(dockerConfigPath, {
254278
auths: {
255279
[registryHost]: {

apps/webapp/test/getDeploymentImageRef.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { describe, expect, it } from "vitest";
2-
import { getDeploymentImageRef, getEcrRegion } from "../app/v3/getDeploymentImageRef.server";
2+
import {
3+
getDeploymentImageRef,
4+
getEcrAuthToken,
5+
getEcrRegion,
6+
} from "../app/v3/getDeploymentImageRef.server";
37
import { ECRClient, DeleteRepositoryCommand } from "@aws-sdk/client-ecr";
48

59
describe.skipIf(process.env.RUN_REGISTRY_TESTS !== "1")("getDeploymentImageRef", () => {
@@ -101,3 +105,35 @@ describe.skipIf(process.env.RUN_REGISTRY_TESTS !== "1")("getDeploymentImageRef",
101105
).rejects.toThrow("Invalid ECR registry host: invalid.ecr.amazonaws.com");
102106
});
103107
});
108+
109+
describe.skipIf(process.env.RUN_REGISTRY_AUTH_TESTS !== "1")("getEcrAuthToken", () => {
110+
const registryId = process.env.DEPLOY_REGISTRY_ID;
111+
const testHost = "123456789012.dkr.ecr.us-east-1.amazonaws.com";
112+
113+
it("should return valid ECR credentials", async () => {
114+
const auth = await getEcrAuthToken({
115+
registryHost: testHost,
116+
registryId,
117+
});
118+
119+
// Check the structure and basic validation of the returned credentials
120+
expect(auth).toHaveProperty("username");
121+
expect(auth).toHaveProperty("password");
122+
expect(auth.username).toBe("AWS");
123+
expect(typeof auth.password).toBe("string");
124+
expect(auth.password.length).toBeGreaterThan(0);
125+
126+
// Verify the token format (should be a base64-encoded string)
127+
const base64Regex = /^[A-Za-z0-9+/=]+$/;
128+
expect(base64Regex.test(auth.password)).toBe(true);
129+
});
130+
131+
it("should throw error for invalid region", async () => {
132+
await expect(
133+
getEcrAuthToken({
134+
registryHost: "invalid.ecr.amazonaws.com",
135+
registryId,
136+
})
137+
).rejects.toThrow();
138+
});
139+
});

0 commit comments

Comments
 (0)