You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(orb): revoke-on-rotate and a self-hoster-reachable revoke path for broker enrollments (#9149)
issueOrbEnrollment was a bare INSERT: it never revoked, updated, or even
counted sibling rows for the same installation_id, so re-running the
install flow after a secret leak minted a SECOND simultaneously-valid
secret and left the leaked one working forever. The only revoke path sat
behind INTERNAL_JOB_TOKEN, unreachable by a self-hosting maintainer.
- issueOrbEnrollment takes an optional { rotate: true }: when set, every
prior live enrollment for the installation is revoked before the new one
is minted. Defaults to false (append) so a blue/green container swap can
still rely on two live enrollments briefly overlapping (#9150's sibling
fix is what makes that overlap safe, not this one).
- The OAuth landing page's callback now recognizes state=<installationId>:
rotate / :revoke (installation_id has no channel back through a bare
GitHub login/oauth/authorize bounce other than state) -- re-proving
admin-of-installation via the SAME verifyInstallationAdmin gate the
enrollment flow already uses, so a maintainer can rotate or revoke
without operator involvement. Revoke deliberately skips the
suspended/self-enrollment-disabled checks: locking down a leaked secret
should never be blocked by unrelated administrative state.
- The secret page now surfaces how many enrollments are live for the
installation and links to the rotate/revoke actions; its response sets
Cache-Control: no-store (it was previously a cacheable GET response
rendering a plaintext secret).
- The internal installations list and enrollment-issue routes gained the
same live-enrollment-count visibility and an optional rotate flag, for
operator-issued parity with the OAuth self-service path.
`SELECT installation_id AS installationId, account_login AS accountLogin, account_type AS accountType,
4586
4593
repository_selection AS repositorySelection, registered, suspended_at AS suspendedAt,
4587
-
removed_at AS removedAt, first_seen_at AS firstSeenAt, last_event_at AS lastEventAt
4594
+
removed_at AS removedAt, first_seen_at AS firstSeenAt, last_event_at AS lastEventAt,
4595
+
(SELECT COUNT(*) FROM orb_enrollments oe WHERE oe.installation_id = orb_github_installations.installation_id AND oe.state = 'enrolled' AND oe.revoked_at IS NULL) AS liveEnrollmentCount
4588
4596
FROM orb_github_installations ORDER BY last_event_at DESC`,
constinstall=awaitenv.DB.prepare("SELECT registered FROM orb_github_installations WHERE installation_id = ?").bind(installationId).first<{registered: number}>();
constresult=awaitenv.DB.prepare("UPDATE orb_enrollments SET revoked_at = CURRENT_TIMESTAMP, state = 'revoked' WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL")
96
+
.bind(installationId)
97
+
.run();
98
+
returnresult.meta.changes;
99
+
}
100
+
101
+
/** How many enrollments are currently live ('enrolled', not revoked) for an installation (#9149) — the count
102
+
* surfaced to an operator (the internal installations list) and to a maintainer (the OAuth secret page) so
103
+
* neither has to guess whether a re-enrollment minted an extra standing credential. */
constrow=awaitenv.DB.prepare("SELECT COUNT(*) AS c FROM orb_enrollments WHERE installation_id = ? AND state = 'enrolled' AND revoked_at IS NULL").bind(installationId).first<{c: number}>();
if(!isAdmin)returnc.html(landingPage(c.env,"Admin access required","You must be an admin of this installation's account to enroll it for self-host."),403);
137
+
// #9149: a REVOKE request skips the active/disabled checks below on purpose — a maintainer revoking a
138
+
// leaked secret must be able to do so EVEN ON an installation the operator has since suspended or disabled;
139
+
// there is no reason unrelated administrative state should block someone locking down their own credential.
140
+
// It also never touches `registered` (no auto-registration side effect for a request that isn't enrolling).
if(install.removed_at!==null||install.suspended_at!==null)returnc.html(landingPage(c.env,"Installation not active","This installation is suspended or uninstalled — re-install the Orb App, then retry."),403);
106
146
if(install.self_enrollment_disabled===1)returnc.html(landingPage(c.env,"Installation disabled","This installation was disabled by the operator — contact the operator to re-enable self-host enrollment."),403);
107
147
// Zero-touch self-service: a verified admin of an ACTIVE, non-disabled install self-registers it (registered=1).
108
148
// installation_id stays bound server-side in the enrollment, so brokered tokens remain scoped to this install.
109
149
if(install.registered!==1){
110
150
awaitc.env.DB.prepare("UPDATE orb_github_installations SET registered = 1, last_event_at = CURRENT_TIMESTAMP WHERE installation_id = ?").bind(installationId).run();
? `<p>This installation now has <strong>${liveCount}</strong> live enrollment secret${liveCount===1 ? "" : "s"}, including this one. <a href="${rotateUrl}">Rotate</a> (revokes every other one and issues this as the only valid secret) or <a href="${revokeUrl}">revoke all</a> if you no longer need self-host access for this installation.</p>`
210
+
: "";
151
211
returnshell(
152
212
"Your enrollment secret",
153
-
`<p>Set this as <code>ORB_ENROLLMENT_SECRET</code> in your self-host <code>.env</code>, then restart the container. It is shown <strong>once</strong> — store it now.</p><pre>${secret}</pre>`,
213
+
`<p>Set this as <code>ORB_ENROLLMENT_SECRET</code> in your self-host <code>.env</code>, then restart the container. It is shown <strong>once</strong> — store it now.</p><pre>${secret}</pre>${manageSection}`,
214
+
);
215
+
}
216
+
217
+
/** Confirms a revoke-all action (#9149) — the self-hoster-reachable counterpart to secretPage. `count` is
218
+
* never user-supplied (it's the return of revokeAllLiveEnrollmentsForInstallation), so it's safe to embed. */
`<p><strong>${count}</strong> live enrollment ${plural} been revoked for this installation. Any self-hosted container still using ${count===1 ? "it" : "one of them"} loses broker access immediately. Re-run the install flow to issue a new one.</p>`,
0 commit comments