Skip to content

Commit ce3df13

Browse files
fix: route Vercel builds through Artifactory via OIDC federation, fix CI
1 parent ceb5d21 commit ce3df13

7 files changed

Lines changed: 249 additions & 159 deletions

File tree

‎.github/workflows/preview-community.yaml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ jobs:
1212
Deploy-Production:
1313
runs-on:
1414
labels: ubuntu-latest
15+
permissions:
16+
contents: read
17+
id-token: write
1518
steps:
1619
- name: Checkout the Codebase
1720
uses: actions/checkout@v4
@@ -30,4 +33,5 @@ jobs:
3033
- name: Deploy on Vercel
3134
id: deploy
3235
run: |
33-
vercel deploy --prod --token=${{ secrets.VERCEL_ACCESS_TOKEN }}
36+
vercel deploy --prod \
37+
--token=${{ secrets.VERCEL_ACCESS_TOKEN }}

‎.github/workflows/preview.yaml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ jobs:
1212
runs-on:
1313
labels: ubuntu-latest
1414
environment: preview
15+
permissions:
16+
contents: read
17+
id-token: write
1518
outputs:
1619
deploymentUrl: ${{ steps.deploy.outputs.deploymentUrl }}
1720
steps:
@@ -22,7 +25,8 @@ jobs:
2225
- name: Deploy on Vercel
2326
id: deploy
2427
run: |
25-
vercel deploy --token=${{ secrets.VERCEL_ACCESS_TOKEN }} > deploy.log
28+
vercel deploy \
29+
--token=${{ secrets.VERCEL_ACCESS_TOKEN }} > deploy.log
2630
URL=$(cat deploy.log | grep -o 'https://[^ ]*.vercel.app' | head -n1)
2731
echo "deploymentUrl=$URL" >> $GITHUB_OUTPUT
2832
- name: Extract branch name

‎.github/workflows/production.yaml‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ jobs:
4646
needs: test
4747
runs-on:
4848
labels: ubuntu-latest
49+
permissions:
50+
contents: read
51+
id-token: write
4952
steps:
5053
- name: Checkout the Codebase
5154
uses: actions/checkout@v4
@@ -64,4 +67,5 @@ jobs:
6467
- name: Deploy on Vercel
6568
id: deploy
6669
run: |
67-
vercel deploy --prod --token=${{ secrets.VERCEL_ACCESS_TOKEN }}
70+
vercel deploy --prod \
71+
--token=${{ secrets.VERCEL_ACCESS_TOKEN }}

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ yarn-error.log*
3737

3838
.npmrc
3939
**/.npmrc
40+
.npmrc.oidc
4041

4142

4243
# Created by https://www.toptal.com/developers/gitignore/api/macos,windows,visualstudiocode,webstorm,intellij,node

‎package-lock.json‎

Lines changed: 156 additions & 156 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/oidc-install.mjs‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env node
2+
import { execSync } from "node:child_process";
3+
import { readFileSync, writeFileSync } from "node:fs";
4+
5+
const JF_URL = "https://a0us.jfrog.io";
6+
const OIDC_PROVIDER_NAME = "vercel";
7+
const ARTIFACTORY_NPM_HOST = "a0us.jfrog.io/artifactory/api/npm/npm";
8+
const PUBLIC_NPM_HOST = "registry.npmjs.org";
9+
const BUILD_NPMRC_PATH = ".npmrc.oidc";
10+
11+
async function exchangeVercelOidcTokenForArtifactoryToken() {
12+
const oidcToken = process.env.VERCEL_OIDC_TOKEN;
13+
if (!oidcToken) {
14+
throw new Error(
15+
"VERCEL_OIDC_TOKEN is not set. Enable OIDC Federation for this " +
16+
"environment in Vercel Project Settings > Security > OIDC Federation."
17+
);
18+
}
19+
20+
const res = await fetch(`${JF_URL}/access/api/v1/oidc/token`, {
21+
method: "POST",
22+
headers: { "Content-Type": "application/json" },
23+
body: JSON.stringify({
24+
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
25+
subject_token_type: "urn:ietf:params:oauth:token-type:id_token",
26+
subject_token: oidcToken,
27+
provider_name: OIDC_PROVIDER_NAME,
28+
}),
29+
});
30+
31+
if (!res.ok) {
32+
throw new Error(
33+
`JFrog OIDC token exchange failed (${res.status}): ${await res.text()}`
34+
);
35+
}
36+
37+
const { access_token } = await res.json();
38+
if (!access_token) {
39+
throw new Error("JFrog OIDC token exchange returned no access_token.");
40+
}
41+
42+
return access_token;
43+
}
44+
45+
function pointLockfileAtArtifactory() {
46+
const lockfile = readFileSync("package-lock.json", "utf8");
47+
const rewritten = lockfile.replaceAll(
48+
`https://${PUBLIC_NPM_HOST}/`,
49+
`https://${ARTIFACTORY_NPM_HOST}/`
50+
);
51+
writeFileSync("package-lock.json", rewritten);
52+
}
53+
54+
// Written fresh for this build only, under --userconfig, so the Artifactory
55+
// registry override never leaks into a plain `npm ci` elsewhere (e.g. the
56+
// GitHub Actions test workflows, which install straight from the public
57+
// registry and have no access to this token).
58+
function writeBuildScopedNpmrc(npmToken) {
59+
writeFileSync(
60+
BUILD_NPMRC_PATH,
61+
`registry=https://${ARTIFACTORY_NPM_HOST}/\n` +
62+
`//${ARTIFACTORY_NPM_HOST}/:_authToken=${npmToken}\n`
63+
);
64+
}
65+
66+
const npmToken = await exchangeVercelOidcTokenForArtifactoryToken();
67+
68+
// Only this ephemeral Vercel build checkout is rewritten to fetch through
69+
// Artifactory; the committed lockfile (used by GitHub Actions' npm ci) is
70+
// untouched and keeps resolving against the public registry.
71+
pointLockfileAtArtifactory();
72+
writeBuildScopedNpmrc(npmToken);
73+
74+
execSync(`npm ci --userconfig ${BUILD_NPMRC_PATH}`, { stdio: "inherit" });

‎vercel.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"installCommand": "node scripts/oidc-install.mjs"
3+
}

0 commit comments

Comments
 (0)