jose

package
v1.17.9 Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

README

pkg/jose

This package is the cryptographic trust anchor for identity, primarily for pkg/oauth2.

Intent

The package owns two tightly related responsibilities:

  • signing-key lifecycle management
  • JOSE/JWT/JWE issue and verification primitives

Its primary consumer is pkg/oauth2, which relies on it to issue and validate the tokens used by the identity service. It is also general enough to issue JWTs for other audiences that can validate them against the service JWKS endpoint.

This is not just a helper around a crypto library. The package defines the live rotation model, the published verification material, and the token compatibility window that higher layers depend on.

Key Lifecycle Model

The root cryptographic material comes from a cert-manager managed TLS secret. JWTIssuer.Run() uses leader election so only one replica manages key rotation state at a time.

The package then projects that live key into the SigningKey resource as a rolling compatibility window:

  • the first key is the current primary
  • the second key is the immediately previous primary

New tokens are always issued with the current primary. Verification and decryption accept either the current or previous key so tokens survive one rotation, but not two.

Invariants

  • pkg/oauth2 is the primary consumer, but the package also supports other JWT/JWE issuing use cases that rely on the same JWKS and rotation model.
  • The cert-manager private key is the source cryptographic material for token issue.
  • SigningKey.Spec.PrivateKeys is an ordered compatibility window, not a passive backup copy.
  • Only one active leader should mutate the SigningKey window.
  • Key ordering is semantically significant: newest first.
  • Issued tokens are expected to survive one key rotation and fail after the signing key is rotated out of the retained window.
  • kid lookup is mandatory for verification and decryption.
  • Token typ values are part of the security model and are used to prevent reuse across token contexts.
  • Signed JWTs use ES512.
  • Encrypted tokens are nested signed-then-encrypted JWTs.
  • The symmetric encryption key used for JWE is intentionally derived from the signing private key.

OAuth2 Relevance

This package should be read as the cryptographic substrate under pkg/oauth2:

  • it publishes JWKS material
  • it signs tokens
  • it encrypts token payloads
  • it defines how long old tokens remain valid across key rotation

Higher layers should not redefine those rules independently.

Caveats

  • This package is coupled to Kubernetes runtime state. It depends on cert-manager managed secrets, leader election, and the SigningKey resource.
  • Signing and encryption material are intentionally coupled. That is a pragmatic design here, but it should not be mistaken for a generic best-practice template.
  • The package contains a transition compatibility path for decrypting older ECDH_ES tokens even though new token issue had already switched to A256GCMKW.
  • ECDH_ES was an expedient choice when originally written, but it is not an acceptable steady-state issue model here. Anyone with the public key material from JWKS can encrypt their own token payloads. The only reason the decode fallback is tolerable as a migration aid is that ES512 signatures are still enforced on the nested token.
  • That ECDH_ES path is a bounded, one-way migration aid for already-issued tokens, not a permanent design requirement or mixed ongoing algorithm support. A256GCMKW issuance landed in commit 194bc975 and first shipped in v1.4.0 on July 31, 2025. The fallback was explicitly added in commit a54875e3 and first shipped in v1.4.1 on August 18, 2025. With the default 90-day token/renewal window from the charts, the conservative safe-removal point was approximately November 16, 2025.

Cross-Repo Context

The package exposes a JWKS-based trust model that can be consumed outside identity itself. Other internal services may legitimately depend on tokens issued here so long as they follow the same verification and rotation assumptions.

Documentation

Index

Constants

View Source
const SigningKeyName = "unikorn-identity-jose"

Variables

View Source
var (
	// ErrKeyFormat is raised when something is wrong with the
	// encryption keys.
	ErrKeyFormat = errors.New("key format error")

	// ErrTokenVerification is raised when token verification fails.
	ErrTokenVerification = errors.New("failed to verify token")

	// ErrMissingKey is raised when a key is missing from a secret.
	ErrMissingKey = errors.New("failed to lookup key")

	// ErrContextError is raised when a required value cannot be retrieved
	// from a context.
	ErrContextError = errors.New("value missing from context")

	// ErrJOSE is raised when something is wrong with a JWT.
	ErrJOSE = errors.New("jose error")
)

Functions

This section is empty.

Types

type CoordinationClientGetter added in v0.2.4

type CoordinationClientGetter interface {
	Client() (coordinationv1.CoordinationV1Interface, error)
}

type InClusterCoordinationClientGetter added in v0.2.4

type InClusterCoordinationClientGetter struct{}

func (*InClusterCoordinationClientGetter) Client added in v0.2.4

type JWTIssuer

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

JWTIssuer is in charge of API token issue and verification. It is expected that the keys come from a mounted kubernetes.io/tls secret, and that is managed by cert-manager. As a result the keys will rotate every 60 days (by default), so you MUST ensure they are not cached in perpetuity. Additionally, due to horizontal scale-out these secrets need to be shared between all replicas so that a token issued by one, can be verified by another. As such if you ever do cache the certificate load, it will need to be coordinated between all instances.

func NewJWTIssuer

func NewJWTIssuer(client client.Client, namespace string, options *Options) *JWTIssuer

NewJWTIssuer returns a new JWT issuer and validator.

func (*JWTIssuer) DecodeJWEToken

func (i *JWTIssuer) DecodeJWEToken(ctx context.Context, tokenString string, claims any, tokenType TokenType) error

func (*JWTIssuer) DecodeJWT added in v0.1.2

func (i *JWTIssuer) DecodeJWT(ctx context.Context, tokenString string, claims any) error

func (*JWTIssuer) EncodeJWEToken

func (i *JWTIssuer) EncodeJWEToken(ctx context.Context, claims any, tokenType TokenType) (string, error)

EncodeJWEToken encodes, signs and encrypts as set of claims. For access tokens this implemenrs https://datatracker.ietf.org/doc/html/rfc9068

func (*JWTIssuer) EncodeJWT

func (i *JWTIssuer) EncodeJWT(ctx context.Context, claims any) (string, error)

func (*JWTIssuer) GetJSONWebKey added in v0.2.39

func (i *JWTIssuer) GetJSONWebKey(pem []byte) (*jose.JSONWebKey, *jose.JSONWebKey, error)

GetJSONWebKey converts from a X.509 secret into a JWK.

func (*JWTIssuer) GetJSONWebKeySet added in v0.2.39

func (i *JWTIssuer) GetJSONWebKeySet(ctx context.Context) (*jose.JSONWebKeySet, *jose.JSONWebKeySet, error)

GetJSONWebKeySet returns all JSON web keys.

func (*JWTIssuer) GetKeyByID added in v0.2.39

func (i *JWTIssuer) GetKeyByID(ctx context.Context, keyID string) (*jose.JSONWebKey, *jose.JSONWebKey, error)

func (*JWTIssuer) GetPrimaryKey added in v0.2.39

func (i *JWTIssuer) GetPrimaryKey(ctx context.Context) (*jose.JSONWebKey, *jose.JSONWebKey, error)

GetPrimaryKey is the JWK used to sign and encrypt new tokens.

func (*JWTIssuer) Run added in v0.2.4

func (i *JWTIssuer) Run(ctx context.Context, coordinationClientGetter CoordinationClientGetter) error

Run starts the certificate management loop. The certificate itself is managed by cert-manager, as a reissue duration of N and a lifetime of 2N. Tokens may be issued for a maximum duration of N. Tokens issued just before the certificates N will be able to be verified until their expiration. Now, to pull this off we need to:

  • Keep a primary copy of the current key pair so we can see when it changes. with reference to the master copy managed by cert-manager.
  • When it does change, we need to demote the primary copy to the secondary, then update the new primary.

Tokens will always be issued by the current primary, but may be verified by either the primary or secondary.

As identity is hoizontally scalable, we have another pain in the arse that is split brain, so use leadership election to ease the burden.

func (*JWTIssuer) StartLeading added in v0.2.4

func (i *JWTIssuer) StartLeading(ctx context.Context)

StartLeading does certificate rotation handling. NOTE: there is a startup penalty waiting for the first tick, but on the first invocation it's expected there won't be any traffic immediately anyway.

func (*JWTIssuer) StopLeading added in v0.2.4

func (i *JWTIssuer) StopLeading()

type NestedJSONWebToken added in v1.4.1

type NestedJSONWebToken struct {
	Headers []jose.Header
	// contains filtered or unexported fields
}

func (*NestedJSONWebToken) Decrypt added in v1.4.1

func (t *NestedJSONWebToken) Decrypt(decryptionKey interface{}) (*jwt.JSONWebToken, error)

type Options

type Options struct {
	// IssuerSecretName is the name of the secret that contains our managed
	// signing key.
	IssuerSecretName string

	// RotationPeriod is used to tweak the period for certificate rotation
	// detection.
	RotationPeriod time.Duration
}

func (*Options) AddFlags

func (o *Options) AddFlags(f *pflag.FlagSet)

AddFlags registers flags with the provided flag set.

type PublicKeyer added in v0.2.39

type PublicKeyer interface {
	Public() crypto.PublicKey
}

type TokenType added in v0.2.4

type TokenType string

TokenType is used to define the specific use of a token.

const (
	// TokenTypeAccessToken is defined by RFC9068 to prevent reuse in other contexts.
	// This is only valid for access tokens.
	TokenTypeAccessToken TokenType = "at+jwt"

	// TokenTypeAuthorizationCode is defined by us to prevent reuse in other contexts.
	// This is only valid for authorization codes.
	//nolint:gosec
	TokenTypeAuthorizationCode TokenType = "unikorn-cloud.org/authcode+jwt"

	// TokenTypeLoginState is defined by us to prevent reuse in other contexts.
	// This is only valid to preserve state across federated authentication.
	//nolint:gosec
	TokenTypeLoginState TokenType = "unikorn-cloud.org/loginstate+jwt"

	// TokenTypeLoginDialogState is defined by us to prevent reuse in other contexts.
	// This is only valid to preserve state across login dialogs.
	//nolint:gosec
	TokenTypeLoginDialogState TokenType = "unikorn-cloud.org/logindialogstate+jwt"

	// TokenTypeRefreshToken is defined to prevent reuse in other contexts.
	// This is only valid for a refresh token.
	//nolint:gosec
	TokenTypeRefreshToken TokenType = "unikorn-cloud.org/rt+jwt"
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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