Help us shape the future of Go! Take the 2026 Go Developer Survey

grants

package
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

README

Management-v1 grant foundation

This package is an API-side issuer and reference verifier. It is not yet wired to a guest listener or a public execution-start route. Enrollment, certificate issuance, mTLS peer validation, guest listener integration, live policy/epoch updates and runner integration remain separate prerequisites.

The guest repository's independent Rust kw-management verifier consumes the byte-identical testdata/management-v1.json interoperability vector. Both sides test the synthetic public-key signature; it is never an installation trust key.

Envelope

The token is a compact JWS with unpadded base64url segments:

base64url(header JSON).base64url(claims JSON).base64url(Ed25519 signature)

The signature covers the exact first two encoded segments and separator. Header values are alg: EdDSA, typ: kw-management-grant+jwt, and kid equal to the first 16 bytes of SHA-256 of the Ed25519 public key, lowercase hex. Verification keys are explicitly pinned; there is no URL/key discovery, symmetric verification, unsigned mode or desktop-ticket compatibility.

Claims have version: 1, issuer kube-workspaces-api/management/v1, audience kube-workspaces/management/v1, random 256-bit jti, integer Unix iat/exp, and a binding object. Lifetime is at most 60 seconds. Expiration is exclusive; future-issued tokens are rejected without a clock-skew allowance.

The binding carries immutable actor UID; namespace, workspace name/UID and provisioning generation; execution/recording IDs; command/script/shell operation; OS identity and independent elevation flag; policy version and revocation epoch; fresh 256-bit guest challenge; SHA-256 of the mTLS API peer certificate DER; and SHA-256 of the exact framed operation request. These digests are lowercase hex. An ordinary grant cannot target root, UID 0 or SYSTEM. The guest must also resolve actual OS identity and enforce privilege boundaries; string validation does not replace OS token/account validation.

Issuance and consumption

Only the API holds the dedicated management private key. Guests receive public keys, including overlapping keys during rotation. Removing a key rejects its grants immediately on the next verification with the updated key ring. Management keys must not reuse session HMAC secrets or desktop grants.

Issuance follows current user/policy evaluation, guest capability validation and successful mandatory recording admission. This package intentionally performs none of those upstream decisions and grants no authority by itself.

The verifier compares the complete signed binding with independently obtained expected values before consuming a grant. Workspace identity/generation and revocation epoch come from guest enrollment and authenticated current state; the peer fingerprint comes from validated mTLS; the guest generates its challenge for each connection; request bytes come from the actual framed operation. Copying the token's claims into expected values would defeat the binding.

Consumption is single-use and atomic, with a bounded replay cache. At capacity, unexpired entries are retained and new grants are denied. A connection must keep its verifier for the lifetime of its challenge; disconnect/restart generates a new challenge. Recreating an empty cache with an old challenge is prohibited. Distributed job idempotency/leases remain necessary in addition to this connection-local replay defense. Long-running process/shell authorization needs fresh grants and live revocation fencing; a consumed start grant is not an unlimited session lease.

Checks

go test -race ./internal/execution/grants
go test ./internal/execution/grants -run '^$' -fuzz FuzzConsume -fuzztime 5s

Documentation

Overview

Package grants defines the independently verifiable management-v1 grant envelope. It is separate from display tickets and session authentication. It does not establish enrollment, mTLS, policy, recording or process admission.

Index

Constants

View Source
const (
	Audience    = "kube-workspaces/management/v1"
	Issuer      = "kube-workspaces-api/management/v1"
	MaxLifetime = 60 * time.Second
)
View Source
const (
	// QGAAudience/QGAIssuer identify the QGA pilot transport separately from
	// the mTLS management endpoint so one transport can never accept the
	// other's grants.
	QGAAudience = "kube-workspaces/management-qga/v1"
	QGAIssuer   = "kube-workspaces-api/management-qga/v1"
	// TransportQGAPodExec names the approved pilot transport: Kubernetes-
	// authenticated pod exec into the current virt-launcher, invoking the
	// fixed installed runner via the QEMU guest agent.
	TransportQGAPodExec = "qga-pod-exec/v1"
	// QGAMaxLifetime bounds one pilot grant. The confirmed pilot deadline is
	// 20 seconds; the grant lifetime covers staging, exec and polling.
	QGAMaxLifetime = 60 * time.Second
)

Variables

View Source
var (
	ErrInvalid  = errors.New("invalid management grant")
	ErrExpired  = errors.New("management grant expired or not yet valid")
	ErrBinding  = errors.New("management grant binding mismatch")
	ErrReplay   = errors.New("management grant already consumed")
	ErrCapacity = errors.New("management grant replay cache at capacity")
)

Functions

func Digest

func Digest(data []byte) string

Digest binds exact request bytes (or a certificate DER encoding). The protocol caller must define the request framing before issuance and guest verification.

func KeyID

func KeyID(public ed25519.PublicKey) string

KeyID is the public verification-key fingerprint, never a session signing key.

func NewChallenge

func NewChallenge() (string, error)

NewChallenge produces a 256-bit, connection-specific challenge.

Types

type Binding

type Binding struct {
	ActorUID               string `json:"actor_uid"`
	Namespace              string `json:"namespace"`
	WorkspaceName          string `json:"workspace_name"`
	WorkspaceUID           string `json:"workspace_uid"`
	ProvisioningGeneration string `json:"provisioning_generation"`
	ExecutionID            string `json:"execution_id"`
	RecordingID            string `json:"recording_id"`
	Operation              string `json:"operation"`
	OSIdentity             string `json:"os_identity"`
	Elevated               bool   `json:"elevated"`
	PolicyVersion          string `json:"policy_version"`
	RevocationEpoch        uint64 `json:"revocation_epoch"`
	Challenge              string `json:"challenge"`
	PeerCertificateSHA256  string `json:"peer_certificate_sha256"`
	RequestSHA256          string `json:"request_sha256"`
}

Binding describes one authorized operation on one authenticated connection. Expected values must come from guest identity, the mTLS peer, the connection's fresh challenge and the actual request, never from the grant being verified.

type BindingQGA

type BindingQGA struct {
	ActorUID               string `json:"actor_uid"`
	Namespace              string `json:"namespace"`
	WorkspaceName          string `json:"workspace_name"`
	WorkspaceUID           string `json:"workspace_uid"`
	ProvisioningGeneration string `json:"provisioning_generation"`
	ExecutionID            string `json:"execution_id"`
	RecordingID            string `json:"recording_id"`
	Operation              string `json:"operation"`
	OSIdentity             string `json:"os_identity"`
	Elevated               bool   `json:"elevated"`
	PolicyVersion          string `json:"policy_version"`
	RevocationEpoch        uint64 `json:"revocation_epoch"`
	Challenge              string `json:"challenge"`
	Transport              string `json:"transport"`
	TransportTargetSHA256  string `json:"transport_target_sha256"`
	RequestSHA256          string `json:"request_sha256"`
}

BindingQGA is the QGA pilot transport binding. It replaces the mTLS peer certificate digest with the fixed transport name plus a digest of the server-resolved target identity (namespace/VM UID/launcher pod UID). The guest cannot independently know the launcher pod UID; the trust anchors for the pilot are the API signature, the authenticated Kubernetes exec path and single-use consumption. The guest does independently check workspace_uid against its enrolled identity.

type Claims

type Claims struct {
	Version   int     `json:"version"`
	Issuer    string  `json:"iss"`
	Audience  string  `json:"aud"`
	ID        string  `json:"jti"`
	IssuedAt  int64   `json:"iat"`
	ExpiresAt int64   `json:"exp"`
	Binding   Binding `json:"binding"`
}

type ClaimsQGA

type ClaimsQGA struct {
	Version   int        `json:"version"`
	Issuer    string     `json:"iss"`
	Audience  string     `json:"aud"`
	ID        string     `json:"jti"`
	IssuedAt  int64      `json:"iat"`
	ExpiresAt int64      `json:"exp"`
	Binding   BindingQGA `json:"binding"`
}

ClaimsQGA is the signed QGA grant payload.

type Signer

type Signer struct {
	// contains filtered or unexported fields
}

Signer owns only the management signing key. Callers must independently check current user grants, platform policy, guest capability and recording admission.

func NewSigner

func NewSigner(private ed25519.PrivateKey) (*Signer, error)

func (*Signer) Issue

func (s *Signer) Issue(binding Binding, now time.Time, lifetime time.Duration) (string, error)

Issue creates one compact EdDSA JWS; there is no symmetric or unsigned mode.

func (*Signer) IssueQGA

func (s *Signer) IssueQGA(binding BindingQGA, now time.Time, lifetime time.Duration) (string, error)

IssueQGA creates one compact EdDSA JWS bound to the QGA pilot transport.

func (*Signer) KeyID

func (s *Signer) KeyID() string

KeyID is the pinned-key fingerprint guests use to select this key.

func (*Signer) PublicKey

func (s *Signer) PublicKey() ed25519.PublicKey

PublicKey returns a copy of the verification key guests pin. It never exposes private material.

func (*Signer) String

func (s *Signer) String() string

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier is a bounded one-time-consumption reference verifier for one guest connection. Guests get only public keys. A disconnect must discard its random challenge; recreating a verifier for the same challenge would permit replay.

func NewVerifier

func NewVerifier(keys map[string]ed25519.PublicKey, capacity int) (*Verifier, error)

func (*Verifier) Consume

func (v *Verifier) Consume(token string, expected Binding, now time.Time) (Claims, error)

Consume verifies signature, time, full request/connection binding, and single use before returning claims. A full replay cache denies rather than evicting unexpired entries. Failed verification never consumes a valid grant.

func (*Verifier) ConsumeQGA

func (v *Verifier) ConsumeQGA(token string, expected BindingQGA, now time.Time) (ClaimsQGA, error)

ConsumeQGA verifies a QGA pilot grant against the expected binding and enforces single use on the same bounded replay state as the mTLS verifier.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL