Retry OIDC provisioner initialization instead of disabling it - #2756
Open
AWildLeon wants to merge 1 commit into
Open
Retry OIDC provisioner initialization instead of disabling it#2756AWildLeon wants to merge 1 commit into
AWildLeon wants to merge 1 commit into
Conversation
An OIDC provisioner gets the openid-configuration and the JWK key set of the identity provider on Init. When the provider could not be reached, for example because step-ca started before the network was up, the provisioner was replaced by an Uninitialized one and never recovered: OIDC authentication kept failing until step-ca was restarted. Init now marks a failure to reach the identity provider as temporary, and the provisioner gets the configuration again when it is used, using an exponential backoff between attempts. Any other initialization error, e.g. an invalid configuration, still disables the provisioner. Fixes smallstep#2724 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
An OIDC provisioner gets the openid-configuration and the JWK key set of the identity provider on Init. When the provider could not be reached, for example because step-ca started before the network was up, the provisioner was replaced by an Uninitialized one and never recovered: OIDC authentication kept failing until step-ca was restarted.
Init now marks a failure to reach the identity provider as temporary, and the provisioner gets the configuration again when it is used, using an exponential backoff between attempts. Any other initialization error, e.g. an invalid configuration, still disables the provisioner.
Fixes #2724
Name of feature:
Automatic recovery of OIDC provisioners after a temporary initialization failure.
Pain or issue this feature alleviates:
step-careaches its identity provider once, at startup. On a machine wherestep-castarts before the network is usable β a common systemd ordering problem on homelab and cloud hosts β that single attempt fails and the provisioner is permanently replaced by anUninitializedone. The process keeps running and looks healthy from the outside,/healthis fine, systemd reports the unit as active, but every OIDC authentication returnsprovisioner "β¦" is disabled due to an initialization errorforever. The only fix is a manual restart, and nothing in the running service indicates that a restart is what's needed.After this change the provisioner retries on its own and the first login after connectivity returns succeeds.
Why is this important to the project (if not answered above):
It removes a class of silent, restart-only failures from the operator's plate. There is no configuration to add and no new failure mode: an unreachable provider is now a self-healing condition rather than a permanent one, while genuinely broken configuration still fails loudly at startup exactly as before.
Is there documentation on how to use this feature? If so, where?
None needed β no configuration surface changes and nothing for a user to enable. Three observable behaviours are worth knowing:
failed to initialize OIDC provisioner "name", it will be retried when the provisioner is used: β¦instead offailed to initialize OIDC provisioner "name": β¦.503withoidc provisioner 'name' is not initialized, instead of401 provisioner "name" is disabled due to an initialization error.5s, doubling to a5mceiling (oidcInitRetryInterval/oidcMaxInitRetryIntervalinauthority/provisioner/oidc.go).Initdeliberately leaves the deadline unset, so the very first request after startup retries immediately β a brief boot-time blip recovers on the first login attempt rather than after a delay.Happy to add a note to the docs site if maintainers want the changed log line and status code written down.
In what environments or workflows is this feature supported?
step-cawith one or more OIDC provisioners, fromca.jsonor from the admin database /linkedca.authorizeToken: x509 sign, SSH sign, revoke and SSH revoke.In what environments or workflows is this feature explicitly NOT supported (if any)?
Init. TheErrRetryInitcontract is general, so another provisioner could opt in later, but none does today.clientID, an unparseable endpoint, bad claims β all still produceUninitialized, unchanged.StoreProvisionerandUpdateProvisionerstill reject a provisioner whose endpoint is unreachable, so an admin gets immediate feedback rather than a silently deferred failure.Uninitializedstate for this case. An unreachable OIDC provider no longer showsstate: Uninitializedin the admin API, because the provisioner stays enabled. The condition is still visible in the startup log and in the503on every request.Supporting links/other PRs/issues:
Notes for review
provisioner.ErrRetryInit, alongside the existingErrAllowTokenReuse/ErrNotImplementedsentinels.Initstill returns an error on an unreachable provider, so callers that only checkerr != nilbehave as before;authority.ReloadAdminResourcesuseserrors.Isto decide whether to disable.configurationandkeyStoremoved under aninitMutex, written only while holding it and read only after a successfulensureInitialized, which also serializes concurrent authorization attempts during an outage instead of letting them stampede the provider.-race, and an authority-level test that an unreachable provider stays enabled while a permanent error still disables. Verified with the full suite,go vet,gofmt, andgolangci-lintusing the shared smallstep config, plus an end-to-end run of the built binary against a mock provider started after the CA, with a control run onmasterconfirming it never recovers.This code is AI-generated. While I kind of understand the codebase (a little), I'm not a Go developer but I tested it.