[Bug]: `step ca provisioner add --ca-config` requires `--ca-url`/`--root` even for purely local/offline edits

Offen
#1,705 0 Kommentare 1 Reaktion 0 zugewiesene Personen Auf GitHub ansehen

Maintainer antworten meist innerhalb von 1 Tag

@vulragrag-star arbeitet bereits daran.

Seit 16.9.2026.

  • #1706 von @vulragrag-star — offen

Bewertung

Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Anfängerfreundlichkeit
20/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Veraltet
Tech-Stack
go
Bereich
cli

Rechercherichtung

Start with command/ca/provisioner/provisioner.go and utils/cautils/client.go, especially newCRUDClient and NewUnauthenticatedAdminClient; compare the local-config path with the behavior described for PR #809. Check the provisioner command tests and add coverage for editing an existing --ca-config without network flags. Done means offline edits work without requiring --ca-url or --root and a hanging CA does not block them; a linked pull request is already open.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

bug needs triage
Steps to Reproduce

Reproduced on 0.24.x–0.30.6; does not reproduce on 0.22.0 (pre-#809 behavior)

Create the ca.json (no step ca init, no defaults.json — the CA has never run) and a root cert, exactly as an offline bootstrap tool would produce them.

cat > /home/step/ca.json <<'EOF'
{
  "root": "/home/step/certs/root_ca.crt",
  "crt": "/home/step/certs/intermediate_ca.crt",
  "key": "awskms:key-id=test-key-id",
  "kms": { "type": "awskms", "uri": "awskms:region=us-east-1" },
  "address": ":9000",
  "dnsNames": ["ca.svc.cluster.local"],
  "db": { "type": "mysql", "dataSource": "user:pass@tcp(host:3306)/", "database": "smallstep" }
}
EOF

step certificate create --profile root-ca --no-password --insecure \
  "Test Root" /home/step/root_ca.crt /home/step/root_ca_key

echo mypassword > /home/step/pass.txt

step ca provisioner add step-issuer --type=JWK --create \
  --x509-default-dur=4368h --x509-min-dur=240h --x509-max-dur=8760h \
  --password-file=/home/step/pass.txt \
  --ca-config /home/step/config/ca.json
Your Environment
  • OS: Alpine Step CLI container running from docker (colima) on MacOS 26
  • step CLI version: 0.30.6
Expected Behavior

Since --ca-config points at an existing, valid ca.json file and there is no CA to talk to, step should edit that file locally without requiring any CA connectivity flags — as it did prior to v0.23.1, and as step ca provisioner add name --type=JWK --create --ca-config ca.json is still documented/shown in some examples without --ca-url.

Create a JWK provisioner and explicitly select the configuration file to update:

    step ca provisioner add cicd --type JWK --create --ca-config /path/to/ca.json

This example is defined in the EXAMPLES section of add.go itself (command/ca/provisioner/add.go#L227-L229), and is reproduced verbatim in:

Neither the source comment, the rendered --help text, nor the public docs page have been updated to add --ca-url/--root to this example since #809 was merged, even though running it as written now fails with error generating admin client: 'step ca provisioner add' requires the '--ca-url' flag.

Actual Behavior

Running this

step ca provisioner add step-issuer --type=JWK --create \
  --x509-default-dur=4368h --x509-min-dur=240h --x509-max-dur=8760h \
  --password-file=/home/step/pass.txt \
  --ca-config /home/step/config/ca.json

results in

error generating admin client: 'step ca provisioner add' requires the '--ca-url' flag

Adding --ca-url then immediately produces a second, similarly unconditional error:

error generating admin client: 'step ca provisioner add' requires the '--root' flag

Supplying both --ca-url (pointing at a Kubernetes Service hostname whose backing CA pod is not yet running/reachable) and --root (pointing at the just-created root cert on disk) does not fail immediately — instead, it can hang for the full HTTP client timeout before finally falling back to the local edit and failing:

step ca provisioner add step-issuer --type=JWK --create \
    --x509-default-dur=4368h --x509-min-dur=240h --x509-max-dur=8760h \
    --password-file=/home/step/pass.txt \
    --ca-config /home/step/config/ca.json \
    --ca-url "https://google.com:19999" \
    --root /home/step/certs/root_ca.crt

client GET https://google.com:19999/admin/admins failed: context deadline exceeded (Client.Timeout exceeded while awaiting headers)

(Waited the full request timeout, then failed outright — the local ca.json edit never happens in this case.)

By contrast, a --ca-url that fails fast (connection refused, e.g. https://127.0.0.1:1, or unresolvable DNS) correctly falls back to the local edit within milliseconds:

step ca provisioner add step-issuer --type=JWK --create \
    --x509-default-dur=4368h --x509-min-dur=240h --x509-max-dur=8760h \
    --password-file=/home/step/pass.txt \
    --ca-config /home/step/config/ca.json \
    --ca-url "https://127.0.0.1:1" \
    --root /home/step/certs/root_ca.crt

✔ CA Configuration: /home/step/config/ca.json
Success! Your `step-ca` config has been updated. ...

So the actual offline edit works fine — but only by accident, and only if the network error surfaces quickly. In a real Kubernetes environment, whether the CA's own not-yet-running Service address resolves to "fast connection refused" vs. "silent/hanging" is not something a bootstrap script can control or rely on, making this workflow flaky.

Additional Context

Root cause (traced in source)

In command/ca/provisioner/provisioner.go, newCRUDClient calls cautils.NewUnauthenticatedAdminClient(cliCtx) unconditionally before ever looking at cfgFile/--ca-config:

// utils/cautils/client.go
func NewUnauthenticatedAdminClient(ctx *cli.Context, opts ...ca.ClientOption) (*ca.AdminClient, error) {
	caURL, err := flags.ParseCaURLIfExists(ctx)
	if err != nil {
		return nil, err
	}
	if caURL == "" {
		return nil, errs.RequiredFlag(ctx, "ca-url")   // <-- hard-fails here if --ca-url absent
	}
	root := ctx.String("root")
	if root == "" {
		root = pki.GetRootCAPath()
		if _, err := os.Stat(root); err != nil {
			return nil, errs.RequiredFlag(ctx, "root")  // <-- hard-fails here if no root cert found
		}
	}
	...
	return ca.NewAdminClient(caURL, opts...)
}

Only once an AdminClient is successfully constructed does provisioner.go's newCRUDClient call .IsEnabled() and inspect the resulting error type to decide whether to fall back to local editing:

// command/ca/provisioner/provisioner.go
func newCRUDClient(cliCtx *cli.Context, cfgFile string) (crudClient, error) {
	unauthAdminClient, err := cautils.NewUnauthenticatedAdminClient(cliCtx)
	if err != nil {
		return nil, fmt.Errorf("error generating admin client: %w", err)
	}
	var netErr *net.OpError
	err = unauthAdminClient.IsEnabled()
	switch {
	case errors.As(err, &netErr) || errors.Is(err, ca.ErrAdminAPINotImplemented):
		// falls back to local ca.json edit
		...
	case errors.Is(err, ca.ErrAdminAPINotAuthorized):
		return cautils.NewAdminClient(cliCtx)
	default:
		return nil, err
	}
}

This logic was introduced in PR #809 ("Check for remote admin api before prompting for creds", commit 4c0891c03ecd06c71afd3cf08716e6d3c558f8d7), first released in v0.23.1. Before that commit, newCRUDClient checked --ca-config/cfgFile first and went straight to the local-edit path (newCaConfigClient) if the file existed — no --ca-url/--root were required at all in that case.

This is the same category of bug previously reported and fixed for step ca renew --offline in #627 ("Offline step ca renew insists on --ca-url and --root parameters") — but that fix was specific to the renew/certificate offline code path (cautils.NewClient + flags.Offline) and does not cover step ca provisioner add/update/remove, which use the separate newCRUDClient path shown above and have no --offline flag at all.

Related issues

  • #627 — Offline step ca renew insists on --ca-url and --root parameters (same category, different command, fixed by gating on --offline)
  • #473 — step ca provisioner add panics on fresh k8s install from helm chart (older, different failure mode, but same general theme of provisioner add being awkward to use against a CA that isn't fully live yet)
Contributing

Vote on this issue by adding a 👍 reaction.
To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already).

Vorherrschende Sprache
Go
Sterne
4.3k
Forks
320
Ø Merge
2 T. 3 Std.
Gemergte PRs (30 T.)
11

Entwicklungsumgebung

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus smallstep/cli

Alle Issues in smallstep/cli

Ähnliche Issues

Weitere Issues zu Go

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.