Documentation
¶
Overview ¶
Package identitytoken resolves and acquires the OIDC identity token used for keyless skill push signing (RFC THV-0080 / #6307). It is CLI-side only: the server never handles credentials, only the already-acquired token forwarded in the push request (skills.PushOptions.IdentityToken).
Index ¶
- Variables
- func Acquire(ctx context.Context, opts Options) (string, error)
- func Ambient(ctx context.Context) (token string, ok bool, err error)
- func CheckTransport(baseURL string) error
- func Interactive(tg oauthflow.TokenGetter) (string, error)
- func NoRedirectClient(base *http.Client) *http.Client
- func Resolve(value string) (string, error)
- type Options
Constants ¶
This section is empty.
Variables ¶
var ErrInsecureTransport = errors.New("refusing to send an OIDC identity token over an unencrypted connection")
ErrInsecureTransport is returned when an identity token would be sent over a transport that does not protect it.
var ErrNoCredential = errors.New("signing required: no signing credential available")
ErrNoCredential is the sentinel returned when Acquire exhausts every rung of the ladder without obtaining a signing credential.
The remediation the user reads is supplied by the calling command through Options.Remediation and wrapped around this sentinel rather than baked in here. The ladder is shared by `thv skill push` and `thv ai-plugin push`, but their flag sets are not: plugin signing is keyless-only, so plugin push defines no --key (#6442). A single hard-coded message naming every option would send half its readers to a flag their command does not have.
Functions ¶
func Acquire ¶
Acquire resolves the identity token to sign a skill or plugin push with, trying each rung of the ladder in order:
- An explicit --identity-token is always resolved and forwarded, even alongside --key — the ambiguity is a conflict for the server to reject (skillsvc.validateSigningInputs), never something to silently arbitrate client-side.
- --key or --no-sign with no --identity-token means the user made an explicit signing choice; Acquire returns "" without attempting ambient or interactive acquisition. Only skill pushes can set Key; plugin pushes reach this rung through --no-sign alone.
- A GitHub Actions ambient OIDC token, when present.
- An interactive browser sign-in, gated by opts.Confirm.
- ErrNoCredential.
func Ambient ¶
Ambient fetches a GitHub Actions ambient OIDC token scoped to the sigstore audience. ok is false with a nil error when the environment doesn't carry the id-token: write request variables — that is simply not running under that permission, not a failure. A request that fails once both variables are present IS an error: the caller expected this to work.
func CheckTransport ¶ added in v0.47.0
CheckTransport reports whether baseURL is a safe destination for an OIDC identity token.
The token is a short-lived bearer credential that Fulcio will exchange for a signing certificate attributed to its subject, so anyone who observes it in flight can sign artifacts as the caller (CWE-319, RFC 6750 §5.1). The API base URL is not necessarily local: TOOLHIVE_API_URL can point the CLI at an arbitrary host, and plain http:// to a remote host puts the token on the wire in cleartext.
HTTPS is accepted, as is plaintext HTTP to a loopback host — the Unix socket and named pipe transports both present as "http://localhost", and on those the bytes never reach a network interface at all. Everything else is refused.
Callers must only invoke this when a token is actually present: an unsigned (--no-sign) push over plaintext HTTP carries no credential and is not this function's business to block.
func Interactive ¶
func Interactive(tg oauthflow.TokenGetter) (string, error)
Interactive obtains an OIDC identity token via an interactive browser sign-in against the public-good Sigstore OAuth instance. tg is injectable for tests (see oauthflow.StaticTokenGetter); production callers pass oauthflow.DefaultIDTokenGetter.
This blocks until the user completes or abandons the browser flow. oauthflow.OIDConnect accepts no context and hardcodes context.Background() internally, so there is no cancellation path to plumb here: the redirect wait self-limits to 120s, but provider discovery, the code exchange, and the out-of-band stdin fallback are all unbounded. That's acceptable because this only runs after an explicit y/N confirmation on a TTY — a human is already present, and Ctrl-C is the exit. Wrapping this in a goroutine would leak one blocked on stdin or a listening socket with no way to cancel it, which is worse than an unbounded wait a human can interrupt themselves.
func NoRedirectClient ¶ added in v0.47.0
NoRedirectClient returns a shallow copy of base that refuses to follow redirects, for requests that carry an identity token in their body.
CheckTransport alone is not enough: it clears the base URL, but an accepted HTTPS or loopback endpoint can answer with a 307 or 308, and those preserve the method and replay the body — Go sets GetBody automatically for the bytes.Reader the JSON encoder produces, so the credential would be re-sent to whatever Location names, including a plaintext remote host. Validating only the first hop would leave the guard trivially bypassable by whoever controls the endpoint the CLI was pointed at.
Refusing outright rather than re-checking each hop: the ToolHive API does not redirect its own endpoints, so there is no legitimate case to preserve, and "no redirects" is a property that cannot be subtly wrong. The refusal happens before the second request is issued, so the token never leaves the origin CheckTransport approved.
func Resolve ¶
Resolve interprets the --identity-token flag value: a path to a file containing the token, or the raw token itself (cosign parity with --key, which already accepts a path). An existing regular file wins; otherwise a JWT-shaped value (two dots) is used as a literal token; otherwise this is an error rather than a guess, so a mistyped path is never silently forwarded to Fulcio as if it were a token.
Types ¶
type Options ¶
type Options struct {
// FlagValue is the raw --identity-token flag value, if given.
FlagValue string
// Key is the --key flag value, if given.
Key string
// NoSign is the --no-sign flag value.
NoSign bool
// Confirm is called only when no token was found ambiently, to ask the
// user (via the CLI's own TTY-gated prompt) whether to open a browser
// for an interactive sign-in. Returning (false, nil) declines or skips
// without error. Required.
Confirm func() (bool, error)
// Remediation names the signing choices the calling command actually
// offers, appended to ErrNoCredential. Optional; omitting it yields the
// bare sentinel, which is terser but still correct. See ErrNoCredential
// for why this is caller-supplied rather than a package constant.
Remediation string
}
Options configures Acquire.