ci: deploy over SSH with a forced command instead of Azure OIDC - #4
Merged
Conversation
The previous subscription was disabled, taking the production VM with it.
On the replacement "Azure for Students" subscription, Entra app registration
is blocked by the university tenant:
az ad app create -> Insufficient privileges to complete the operation
The account is Owner on the subscription, but app registration is a directory
permission that Azure RBAC cannot grant, so there is no service principal and
therefore no OIDC federation and no client secret. The keyless deploy cannot
be recreated on this account.
Replaces it with a dedicated deploy key restricted to a forced command on the
host, so the key can trigger a deploy of one commit and nothing else - no
shell, no arbitrary commands, no port forwarding. The host key is pinned via
a secret rather than trusted on first use.
Also in deploy.sh:
- Capture COMMIT_SHA before sourcing .env. .env carries a COMMIT_SHA from the
previous deploy, and "set -a; . .env" would overwrite the incoming value,
silently redeploying the old commit on every run.
- Require COMMIT_SHA rather than defaulting to a "latest" tag that this
pipeline never publishes.
- Make GITHUB_TOKEN optional. The repository and its GHCR images are public,
and the forced command deliberately passes no token.
- Stop leaving a tokenised remote URL in .git/config.
Rationale and the security trade-off are recorded in
docs/adr/adr_02_ssh_deploy.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restores automated deployment after the subscription change. Rationale is recorded in
docs/adr/adr_02_ssh_deploy.md.Why the OIDC deploy can't come back
On the replacement "Azure for Students" subscription:
The account is Owner on the subscription, but app registration is an Entra directory permission and the university tenant blocks it — Azure RBAC can't grant it. No app registration means no service principal, so neither OIDC federation nor a client secret is available.
What replaces it
A dedicated deploy key restricted to a forced command on the host:
The wrapper is root-owned, lives outside the git checkout, and accepts only a bare commit SHA:
Verified on the host:
"whoami""rm -rf /"The host key is pinned via
DEPLOY_KNOWN_HOSTSwithStrictHostKeyChecking=yes, so there's no trust-on-first-use window.Three bugs fixed in deploy.sh along the way
COMMIT_SHA was being clobbered.
.envon the host carries aCOMMIT_SHAfrom the previous deploy, andset -a; . .envoverwrote the incoming value — every deploy would have silently redeployed the old commit. The incoming value is now captured first and takes precedence.COMMIT_SHAis now required. It previously fell back to alatesttag that this pipeline has never published.GITHUB_TOKENis now optional, and the tokenised remote URL is no longer left behind in.git/config.Secrets
DEPLOY_HOST,DEPLOY_USER,DEPLOY_SSH_KEY,DEPLOY_KNOWN_HOSTSare set. The sixAZURE_*secrets are now dead — they point at a disabled subscription and a destroyed host — and should be deleted.AZURE_SSH_KEYis a private key with no remaining purpose.Merge order matters
Merge #3 (docker GID) first. The first SSH deploy runs
git reset --hard origin/main, which will overwrite the host-local GID patch. If this merges first, the Docker panel breaks until #3 lands.Security note
This is a genuine downgrade from OIDC: a long-lived private key now sits in GitHub secrets where previously nothing durable was stored. That's the cost of the tenant restriction, not a preference. The forced command limits a compromise to "can trigger a deploy of an already-built commit" rather than shell access. If app registration ever becomes available, the previous implementation is in git history.