Skip to content

feat(git)!: Tell misconfiguration apart from bad grants and transient failures - #171

Merged
vaind merged 3 commits into
mainfrom
ivan/git-grant-key-helpers-efed4b
Oct 30, 2026
Merged

vaind merged 3 commits into
mainfrom
ivan/git-grant-key-helpers-efed4b

Conversation

@vaind

@vaind vaind commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Porting Sentry and Seer to the Ed25519 grants from #170 found gaps that should be fixed before 3.0.0:

  • A bad key raised plain ValueError/TypeError from the same calls that parse untrusted grants, so callers couldn't tell their own misconfiguration apart from a bad or oversized grant.
  • There was no helper to make a key, so bin/git-server and every test suite copied the same cryptography calls.
  • Three permanent failures answered 502, which Seer's checkout (getsentry/seer#9229) retries for about a minute before failing.

Follow-up to #170 (CW-2334).

Approach

  • New scm.errors.GitGrantKeyError(SCMError, ValueError) for every bad key, on both sides. It isn't an SCMCodedError, because a bad key is the server's fault: GitServer raises it from its constructor rather than answering requests with a 403. An oversized grant still raises a plain ValueError.
  • New require_private_key(Secret) -> Secret, the signer's counterpart to require_public_keys, to check a key at startup.
  • New generate_private_key() -> Secret, which replaces the copied cryptography calls in bin/git-server and make_key_pair.
  • GitServer answers git_remote_not_https, git_remote_refused and resource_certificate_invalid with a 500 instead of a 502. The integration or host is misconfigured, so no retry helps. 502 still means the provider or the token failed; 503 and 504 still mean a retry can succeed.
  • GetToken and GitServer now document that get_token may raise a coded error, which GitServer answers by its code. Sentry relies on this to answer an unreachable control silo with a 503.

Not included, as decided: a public key_id(), or reporting which key matched a grant.

Release notes

Craft uses this section for the changelog in place of the title. The last item covers earlier PRs whose titles don't show a breaking change.

Changelog Entry

  • Raise GitGrantKeyError for a missing or unusable git grant key, so callers can tell their own misconfiguration apart from a bad grant
    • It's a ValueError, not an SCMCodedError. A public key that isn't a string used to raise TypeError.
    • verify_grant([], token) raises it. 2.x refused the grant with GitInvalidGrant (a 403 from GitServer).
  • Add require_private_key to check a signing key at startup, and generate_private_key to make one for tests
  • Answer a remote that isn't https, a refused remote host and an untrusted TLS certificate with a 500 instead of a 502, since no retry can fix them
  • Document that get_token may raise a coded error, which GitServer answers by its code, e.g. ResourceServiceUnavailable as a 503
  • Upgrade notes for earlier 3.0.0 changes
    • GitServer's get_token must return an scm.Secret, or every relayed request fails with TypeError (#167).
    • Once Sentry maps RestrictedIPAddress to GitRemoteRefused with GitRelay(map_error=...), its refusal of internal addresses changes from a 503 to a 500 (#168).

Tests

Each bad-key case raises GitGrantKeyError on both sides, and no message quotes the key. That check also now works for multi-line keys: it used to join the lines, so it never matched RSA, EC or Ed448 keys. A generated key round-trips through sign_grant, public_key_of and verify_grant. New GitServer tests cover the three 500s and a coded and an uncoded error from get_token. The suite, ruff, mypy and prek pass, and I smoke-tested bin/git-server's key loading.

🤖 Generated with Claude Code

Porting Sentry and Seer to the Ed25519 grants showed that neither could tell
its own misconfiguration from a bad or oversized grant. A bad key raised plain
ValueError or TypeError from the same calls that parse untrusted grants. So
Sentry called require_public_keys before every verify_grant and caught both
types, and Seer called public_key_of only to check its key at startup.

GitGrantKeyError now covers every bad-key case on both sides. It's a
ValueError, so existing handlers still catch it, but not an SCMCodedError: a
bad key is the server's fault, and a coded error would be answered as a
refused grant. The oversized-grant error stays a plain ValueError.
require_private_key gives signers the startup check that verifiers already
had, and generate_private_key replaces the cryptography calls that
bin/git-server, the tests and both consumers' tests each copied.

The key-material tests now check each base64 line on its own. Before, they
joined the lines into one string, which never matched a multi-line key (RSA,
EC, Ed448), so those cases passed without checking anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vaind
vaind marked this pull request as ready for review October 30, 2026 07:52
@vaind
vaind requested a review from a team as a code owner October 30, 2026 07:52
A remote that isn't https, a host the relay's adapter refuses, and a TLS
certificate Sentry doesn't trust are misconfigurations of the integration or
the host, and no retry fixes them. As 502s, Seer's checkout retried each one
for about a minute before failing, because it retries a 502 or a 503. As 500s,
it fails at once, like the other misconfigurations already mapped to 500. The
502s that stay mean the provider failed or Sentry couldn't get or use a
token. 503 and 504 still mean a retry can succeed.

GitServer already answered a coded error from get_token by its code, but
nothing said so. Sentry now relies on it to turn an unreachable control silo
into a 503 that Seer retries, so GetToken and GitServer document it and a test
pins it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vaind vaind changed the title feat(git)!: Tell a bad git grant key apart from a bad grant feat(git)!: Tell misconfiguration apart from bad grants and transient failures Oct 30, 2026
Right after "a client closing its request is not reported", "answered the
same way" read as "not reported either". GitServer does report it, and the
test checks that. Also drops a test comment about the HS256 behavior, which
the 3.0.0 release notes record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vaind
vaind merged commit 7b3ec30 into main Oct 30, 2026
14 checks passed
vaind pushed a commit to getsentry/sentry that referenced this pull request Oct 30, 2026
Bumps `sentry-scm` from 2.0.0 to
[3.0.0](https://github.com/getsentry/scm-platform/releases/tag/3.0.0).
The lockfile also picks up `cryptography` as a direct dependency of
sentry-scm; the locked `cryptography` (50.0.0) and `pyjwt` (2.15.0)
already meet 3.0.0's new floors (`>=48.0.1`, `>=2.14.0`).

No code changes are needed on master. The 3.0.0 breaking changes are all
in `scm.git` (Ed25519 grants, `GitServer(public_keys=...)`, `get_token`
returning an `scm.Secret`, `GitRelay(map_error=...)`,
`GitGrantKeyError`, and the 502 → 500 status changes), and nothing on
master uses `scm.git` yet. The only user is the git endpoint in #128444,
which is already written against 3.0.0 and only needs this bump to pass
CI. The one other change that touches master's code,
getsentry/scm-platform#169, is backward compatible for `RpcServer`'s
callers.

Landing this first lets #128444 drop its "bump sentry-scm" checklist
item after a rebase. If you'd rather keep the bump inside #128444, close
this one.

This PR needs no deploy config or secrets. `SCM_GIT_GRANT_PUBLIC_KEYS`
(Ed25519 public keys as PEM, matching Seer's signing key) comes in with
#128444.

Upstream: getsentry/scm-platform#168, getsentry/scm-platform#170,
getsentry/scm-platform#171

Refs CW-2222

<!-- junior-request-attribution:start -->
via **Ivan Dlugos**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:agent%3A19a84388325b8be5736899297f85ece4 -->

--

[View
Session](https://junior-prod.sentry.dev/conversations/agent%3A19a84388325b8be5736899297f85ece4)
[[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3A19a84388325b8be5736899297f85ece4/?project=4510944073809921)

<!-- junior-session-footer:end -->

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
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.

2 participants