clortho

package module
v0.0.4 Latest Latest
Warning

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

Go to latest
Published: Aug 17, 2022 License: Apache-2.0 Imports: 26 Imported by: 8

README

clortho

clortho provides clientside management for cryptographic keys.

Build Status Dependency Updateer codecov.io Go Report Card Quality Gate Status Apache V2 License GitHub Release GoDoc

Summary

clortho manages cryptographic keys, either locally supplied or remotely hosted.

Table of Contents

Code of Conduct

This project and everyone participating in it are governed by the XMiDT Code Of Conduct. By participating, you agree to this Code.

Details

Install

go get -u github.com/xmidt-org/clortho

Contributing

Refer to CONTRIBUTING.md.

Documentation

Overview

Package clortho provides key management for clients.

The two most important types in this package are the Resolver and the KeyRing. A KeyRing is essentially a local cache of keys, accessible via the key's kid (key ID) attribute. A Resolver resolves keys by key ID from an external source, optionally using a KeyRing as a cache.

A Refresher can be used to asynchronously update keys in one or more KeyRing instances or arbitrary client code that handles refresh events.

Index

Constants

View Source
const (
	// DefaultRefreshInterval is used as the base interval between key refreshes when an
	// interval couldn't be determined any other way.
	DefaultRefreshInterval = time.Hour * 24

	// DefaultRefreshMinInterval is the hard minimum for the base interval between key refreshes
	// regardless of how the base interval was determined.
	DefaultRefreshMinInterval = time.Minute * 10

	// DefaultRefreshJitter is the default randomization factor for key refreshes.
	DefaultRefreshJitter = 0.1
)
View Source
const (
	// MediaTypeJSON is the media type for JSON data.  By default, content with this media type
	// may contain either a single JWK or a JWK set.
	MediaTypeJSON = "application/json"

	// SuffixJSON is the file suffix for JSON data.  By default, files with this suffix may
	// contain either a single JWK or a JWK Set.
	SuffixJSON = ".json"

	// MediaTypeJWK is the media type for a single JWK.
	MediaTypeJWK = "application/jwk+json"

	// SuffixJWK is the file suffix for a single JWK.
	SuffixJWK = ".jwk"

	// MediaTypeJWKSet is the media type for a JWK set.
	MediaTypeJWKSet = "application/jwk-set+json"

	// SuffixJWKSet is the file suffix for a JWK set.
	SuffixJWKSet = ".jwk-set"

	// MediaTypePEM is the media type for a PEM-encoded key.
	MediaTypePEM = "application/x-pem-file"

	// SuffixPEM is the file suffix for a PEM-encoded key.
	SuffixPEM = ".pem"
)
View Source
const (
	// KeyIDParameter is the name of the URI template parameter for expanding key URIs.
	KeyIDParameterName = "keyID"
)

Variables

View Source
var (
	// ErrRefresherStarted is returned by Refresher.Start if the Refresher is running.
	ErrRefresherStarted = errors.New("That refresher has already been started")

	// ErrRefresherStopped is returned by Refresher.Stop if the Refresher is not running.
	ErrRefresherStopped = errors.New("That refresher is not running")
)
View Source
var (
	// ErrNoTemplate indicates that no URI template is available for that Resolver's method.
	ErrNoTemplate = errors.New("No URI template expander has been configured for that method.")

	// ErrKeyNotFound indicates that a key could not be resolved, e.g. a key ID did not exist.
	ErrKeyNotFound = errors.New("No such key exists")
)

Functions

This section is empty.

Types

type CancelListenerFunc

type CancelListenerFunc func()

CancelListenerFunc removes the listener it's associated with and cancels any future events sent to that listener.

A CancelListenerFunc is idempotent: after the first invocation, calling this closure will have no effect.

type Config

type Config struct {
	// Resolve is the subset of configuration that establishes how individual
	// keys will be resolved (or, fetched) on demand.
	Resolve ResolveConfig `json:"resolve" yaml:"resolve"`

	// Refresh is the subset of configuration that configures how keys are
	// refreshed asynchronously.
	Refresh RefreshConfig `json:"refresh" yaml:"refresh"`
}

Config configures clortho from (possibly) externally unmarshaled locations.

type ContentMeta

type ContentMeta struct {
	// Format describes the type of key content.  This will typically be either
	// a file suffix (e.g. .pem, .jwk) or a media type (e.g. application/json, application/json+jwk).
	// A custom Loader is free to produce its own format values, which must be
	// understood by a corresponding Parser.
	Format string

	// TTL is the length of time this content is considered current.  A Refresher will
	// use this value to determine when to load content again.
	TTL time.Duration

	// LastModified is the modification timestamp of the content.  For files, this will be
	// the FileInfo.ModTime() value.  For HTTP responses, this will be the Last-Modified header.
	//
	// In the case of HTTP, this field is also used to supply a Last-Modified header in the
	// request.
	LastModified time.Time
}

ContentMeta holds metadata about a piece of content.

type Expander

type Expander interface {
	// Expand takes a value map and returns the URI resulting from that expansion.
	Expand(interface{}) (string, error)
}

Expander is the strategy for expanding a URI template.

func NewExpander

func NewExpander(rawTemplate string) (Expander, error)

NewExpander constructs an Expander from a URI template.

type Fetcher

type Fetcher interface {
	// Fetch grabs keys from a URI.  The prev ContentMeta may either be an empty struct, e.g. ContentMeta{},
	// or the ContentMeta from a previous call to Fetch.
	//
	// This method ensures that each key has a key ID.  For keys that do not have a key ID from their source,
	// a key ID is generated using a thumbprint hash.
	Fetch(ctx context.Context, location string, prev ContentMeta) (keys []Key, next ContentMeta, err error)
}

Fetcher handles fetching keys from URI locations. This is the typical application-layer interface. Generally, clients should use this interface over Loader and Parser.

func NewFetcher

func NewFetcher(options ...FetcherOption) (Fetcher, error)

NewFetcher produces a Fetcher from a set of configuration options.

type FetcherOption

type FetcherOption interface {
	// contains filtered or unexported methods
}

FetcherOption is a configuration option passed to NewFetcher.

func WithKeyIDHash

func WithKeyIDHash(h crypto.Hash) FetcherOption

WithKeyIDHash sets the cryptographic hash used to generate key IDs for keys which do not have them. By default, crypto.SHA256 is used.

func WithLoader

func WithLoader(l Loader) FetcherOption

WithLoader defines the Loader strategy for a Fetcher. By default, a Fetcher uses a Loader created with no options.

func WithParser

func WithParser(p Parser) FetcherOption

WithParser defines the Parser strategy for a Fetcher. By default, a Fetcher uses a Parser created with no options.

type FileLoader

type FileLoader struct {
	// Root is the relative root against which all location paths are resolved.
	// This field is required.
	//
	// By default, the Loader create via NewLoader uses os.DirFS("/") for this
	// field.
	Root fs.FS
}

FileLoader is a Loader implementation that reads content from a file system. All location paths are relative to a supplied root.

func (FileLoader) LoadContent

func (fl FileLoader) LoadContent(_ context.Context, location string, meta ContentMeta) ([]byte, ContentMeta, error)

type HTTPClient

type HTTPClient interface {
	Do(*http.Request) (*http.Response, error)
}

HTTPClient is the minimal interface required by a component which can handle HTTP transactions with a server. *http.Client implements this interface.

type HTTPEncoder

type HTTPEncoder func(context.Context, *http.Request) error

HTTPEncoder is a strategy closure type for modifying an HTTP request prior to issuing it through a client.

type HTTPLoader

type HTTPLoader struct {
	// Client is the HTTP client used to transact with HTTP servers.
	// If unset, http.DefaultClient is used.
	Client HTTPClient

	// Encoders holds an optional slice of HTTPEncoder instances that are used
	// to modify requests prior to sending them to the Client.
	Encoders []HTTPEncoder

	// Timeout is an optional timeout for each HTTP operation.  If unset,
	// no timeout is used.
	Timeout time.Duration
}

HTTPLoader is a Loader strategy for obtaining content from HTTP servers.

func (HTTPLoader) LoadContent

func (hl HTTPLoader) LoadContent(ctx context.Context, location string, meta ContentMeta) ([]byte, ContentMeta, error)

type HTTPLoaderError

type HTTPLoaderError struct {
	Location   string
	StatusCode int
}

HTTPLoaderError indicates that an error occurred when transacting with a HTTP-based source of key material.

func (*HTTPLoaderError) Error

func (hle *HTTPLoaderError) Error() string

type InvalidFormatError added in v0.0.4

type InvalidFormatError struct {
	Format string
}

InvalidFormatError indicates that a format cannot be associated with a Parser because the format string is invalid. For example, format strings that contain semi-colons (;) are invalid because matching a Parser by MIME parameters is not supported.

func (InvalidFormatError) Error added in v0.0.4

func (ife InvalidFormatError) Error() string

Error satisfies the error interface.

type JWKKeyParser

type JWKKeyParser struct {
	Options []jwk.ParseOption
}

JWKKeyParser parses content as a single JWK.

func (JWKKeyParser) Parse

func (jkp JWKKeyParser) Parse(_ string, data []byte) ([]Key, error)

Parse expects data to be a single JWK. If data is a JWK set, this method returns an error.

type JWKSetParser

type JWKSetParser struct {
	Options []jwk.ParseOption
}

JWKSetParser parses content as a JWK set.

func (JWKSetParser) Parse

func (jsp JWKSetParser) Parse(_ string, data []byte) ([]Key, error)

Parse allows data to be either a single JWK or a JWK set. For a single JWK, a 1-element slice is returned.

type Key

type Key interface {
	Thumbprinter

	// KeyID is the identifier for this Key.  This method corresponds to the kid field of a JWK.
	// Note that a KeyID is entirely optional.  This method can return the empty string.
	KeyID() string

	// KeyType is the type of this Key, e.g. EC, RSA, etc.  This method corresponds to
	// the kty field of a JWK.
	//
	// A KeyType is required.  This method always returns a non-empty string.
	KeyType() string

	// KeyUsage describes how this key is allowed to be used.  This method corresponds to
	// the use field of a JWK.
	//
	// A KeyUsage is optional.  This method can return the empty string.
	KeyUsage() string

	// Raw is the raw key, e.g. *rsa.PublicKey, *rsa.PrivateKey, etc.  This is the actual underlying
	// cryptographic key that should be used.
	Raw() interface{}

	// Public is the public portion of the raw key.  If this key is already a public key, this method
	// returns the same key as Raw.
	Public() crypto.PublicKey
}

Key is the minimal interface for cryptographic keys. Once created, a Key is immutable.

func EnsureKeyID

func EnsureKeyID(k Key, h crypto.Hash) (updated Key, err error)

EnsureKeyID conditionally assigns a key ID to a given key. The updated Key is returned, along with any error from the hash.

If k already has a key ID, it is returned as is with no error.

If k does not have a key ID, a thumbprint is generated using the supplied hash. The returned key will be a copy of k with the newly generated key ID. If an error occurred, then k is returned as is.

type KeyAccessor

type KeyAccessor interface {
	// Get returns the Key associated with the given key identifier (kid).
	// If there is no such key, the second return is false.
	Get(keyID string) (Key, bool)

	// Len returns the number of keys currently in this collection.
	Len() int
}

KeyAccessor is a read-only interface to a set of keys.

type KeyRing

type KeyRing interface {
	KeyAccessor
	RefreshListener

	// Add allows ad hoc keys to be added to this ring.  Any key that has
	// no key ID will be skipped.
	//
	// This method returns the actual count of keys added.  This will include
	// keys already in the ring, since those will be overwritten with the new Key object.
	Add(...Key) int

	// Remove allows add hoc keys to be removed from this ring.  Any key ID that isn't
	// in this ring is ignored.  The actual count of deleted keys is returned.
	Remove(keyIDs ...string) int
}

KeyRing is a client-side cache of keys. Implementations are always safe for concurrent access.

A KeyRing can consume events from a Refresher, which will update the ring's set of keys.

func NewKeyRing

func NewKeyRing(initialKeys ...Key) KeyRing

NewKeyRing constructs a KeyRing with an optional set of initial keys. Any key that has no key ID is skipped.

type Keys

type Keys []Key

Keys is a sortable slice of Key instances. Sorting is done by keyID, ascending. Keys with no keyID are sorted after those that have a keyID.

func (Keys) AppendKeyIDs added in v0.0.3

func (ks Keys) AppendKeyIDs(v []string) []string

AppendKeyIDs appends the key Id of each key to the supplied slice, then returns the result.

func (Keys) Len

func (ks Keys) Len() int

Len returns the count of Key instances in this collection.

func (Keys) Less

func (ks Keys) Less(i, j int) bool

Less tests if the Key at i is less than the one at j.

func (Keys) Swap

func (ks Keys) Swap(i, j int)

Swap switches the positions of the Keys at i and j.

type Loader

type Loader interface {
	// LoadContent retrieves the key content from location.  Location must be a URL parseable
	// with url.Parse.
	//
	// This method returns a ContentMeta describing useful characteristics of the content, mostly around
	// caching.  This returned metadata can be passed to subsequent calls to make key retrieval more
	// efficient.
	LoadContent(ctx context.Context, location string, meta ContentMeta) ([]byte, ContentMeta, error)
}

Loader handles the retrieval of content from an external location.

func NewLoader

func NewLoader(options ...LoaderOption) (Loader, error)

NewLoader builds a Loader from a set of options.

By default, the returned Loader handles http, https, and file locations. The default loader, when there is no scheme, is a file loader.

type LoaderOption

type LoaderOption interface {
	// contains filtered or unexported methods
}

LoaderOption represents a configurable option for building a Loader.

func WithSchemes

func WithSchemes(l Loader, schemes ...string) LoaderOption

WithSchemes registers a loader as handling one or more URI schemes. Use this to add custom schemes or to override one of the schemes a loader handles by default.

By default, a Loader created with NewLoader handles the file, http, and https schemes, as well as file paths without a scheme.

type NotAFileError

type NotAFileError struct {
	Location string
}

NotAFileError indicates that a file URI didn't refer to a system file, but instead referred to a directory, pipe, etc.

func (*NotAFileError) Error

func (nafe *NotAFileError) Error() string

type Parser

type Parser interface {
	// Parse parses data, expected to be in the given format, into zero or more Keys.
	// If only one key is present in the data, this method returns a 1-element slice.
	//
	// Format is an opaque string which used as a key to determine which parsing algorithm
	// to apply to the data.  Most commonly, format is either a file suffix (including the
	// leading '.') or a media type such as application/json.  If format contains any MIME
	// parameters, e.g. text/xml;charset=utf-8, they are ignored.
	//
	// Custom parsers should usually avoid trying to validate format.  This is because
	// a Parser might be registered with a nonstandard format.  The format is available to
	// custom parser code primarily for debugging.
	Parse(format string, data []byte) ([]Key, error)
}

Parser turns raw data into one or more Key instances.

func NewParser

func NewParser(options ...ParserOption) (Parser, error)

NewParser returns a Parser tailored with the given options.

The returned Parser handles the following formats by default:

application/json
application/jwk+json
application/jwk-set+json
application/x-pem-file
.json
.jwk
.jwk-set
.pem

A caller can use WithFormats to change the parser associated with a format or to register a Parser for a new, custom format.

type ParserOption

type ParserOption interface {
	// contains filtered or unexported methods
}

ParserOption allows tailoring of the Parser returned by NewParser.

func WithFormats

func WithFormats(p Parser, formats ...string) ParserOption

WithFormats associates a Parsers with one or more formats. Each format is an opaque string simply used as a way to look up a parsing algorithm. Typically, a format is a file suffix (including the leading '.') or a media type such as application/json.

type RefreshConfig

type RefreshConfig struct {
	// Sources are the set of refresh sources to be polled for key material.
	//
	// If this slice is empty, a Refresher is still created, but it will
	// do nothing.
	//
	// If there are multiple sources with the same URI, an error is raised.
	Sources []RefreshSource `json:"sources" yaml:"sources"`
}

RefreshConfig configures all aspects of key refresh.

type RefreshEvent

type RefreshEvent struct {
	// URI is the source of the keys.
	URI string

	// Err is the error that occurred while trying to interact with the URI.
	// This field can be nil to indicate no error.  When this field is non-nil,
	// the Keys field will be populated with the last known valid set of keys
	// from the given URI.
	Err error

	// Keys represents the complete set of keys from the URI.  When Err is not nil,
	// this field will be set to the last known valid set of keys.
	//
	// This field will be sorted by KeyID.
	Keys Keys

	// New are the keys that a brand new with this event.  These keys will be
	// included in the Keys field.
	//
	// This field will be sorted by KeyID.
	New Keys

	// Deleted are the keys that are now missing from the refreshed keys.
	// These keys will not be in the Keys field.  These keys will have been present
	// in the previous event(s).
	//
	// This field will be sorted by KeyID.
	Deleted Keys
}

RefreshEvent represents a set of keys from a given URI that has been asynchronously fetched.

type RefreshListener

type RefreshListener interface {
	// OnRefreshEvent receives a refresh event.  This method must not panic.
	OnRefreshEvent(RefreshEvent)
}

RefreshListener is a sink for RefreshEvents.

type RefreshSource

type RefreshSource struct {
	// URI is the location where keys are served.  By default, clortho supports
	// file://, http://, and https:// URIs, as well as standard file system paths
	// such as /etc/foo/bar.jwk.
	//
	// This field is required and has no default.
	URI string `json:"uri" yaml:"uri"`

	// Interval is the base time between refreshing keys from this source.  This value
	// is used when the source URI doesn't specify any sort of time-to-live or expiry.
	// For example, if an http source doesn't specify a Cache-Control header, this value is used.
	//
	// If this field is not positive, DefaultRefreshInterval is used.
	Interval time.Duration `json:"interval" yaml:"interval"`

	// MinInterval specifies the absolute minimum time between key refreshes from this source.
	// Regardless of HTTP headers, the Interval field, etc, key refreshes will not occur more
	// often than this field indicates.
	//
	// If this value is not positive, DefaultRefreshMinInterval is used.
	MinInterval time.Duration `json:"minInterval" yaml:"minInterval"`

	// Jitter is the randomization factor applied to the interval between refreshes.  No matter how
	// the interval is determined (e.g. Cache-Control, Interval field, etc), a random value between
	// [1-Jitter,1+Jitter]*interval is used as the actual time before the next attempted refresh.
	//
	// Valid values are between 0.0 and 1.0, exclusive.  If this value is outside that range,
	// including being unset, DefaultRefreshJitter is used instead.
	Jitter float64 `json:"jitter" yaml:"jitter"`
}

RefreshSource describes a single location where keys are retrieved on a schedule.

type Refresher

type Refresher interface {
	// Start bootstraps tasks that fetch keys and dispatch events to any listeners.
	// Keys will arrive asynchronously to any registered listeners.
	//
	// If this Refresher has already been started, this method returns ErrRefresherStarted.
	Start(context.Context) error

	// Stop shuts down all refresh tasks.
	//
	// If this Refresher is not running, this method returns ErrRefresherStopped.
	Stop(context.Context) error

	// AddListener registers a channel that receives refresh events.  No caching of events
	// is done.  The supplied listener will receive events the next time any of the key
	// sources are queried.
	//
	// The returned closure can be used to cancel refreshes sent to the listener.  Clients
	// are not required to use this closure, particularly if the listener is active for the
	// life of the application.
	AddListener(l RefreshListener) CancelListenerFunc
}

Refresher handles asynchronously refreshing sets of keys from one or more sources.

func NewRefresher

func NewRefresher(options ...RefresherOption) (Refresher, error)

NewRefresher constructs a Refresher using the supplied options. Without any options, a default Loader and Parser are created and used.

type RefresherOption

type RefresherOption interface {
	// contains filtered or unexported methods
}

RefresherOption is a configurable option passed to NewRefresher.

func WithSources

func WithSources(sources ...RefreshSource) RefresherOption

WithSources associates external sources of keys with a Refresher. This option is cumulative: all sources from each call to WithSources will be added to the configured Refresher.

type ResolveConfig

type ResolveConfig struct {
	// Template is a URI template used to fetch keys.  This template may
	// use a single parameter named keyID, e.g. http://keys.com/{keyID}.
	Template string `json:"template" yaml:"template"`

	// Timeout refers to the maximum time to wait for a refresh operation.
	// There is no default for this field.  If unset, no timeout is applied.
	Timeout time.Duration `json:"timeout" yaml:"timeout"`
}

ResolveConfig configures how to fetch individual keys on demand.

type ResolveEvent

type ResolveEvent struct {
	// URI is the actual, expanded URI used to obtain the key material.
	URI string

	// KeyID is the key ID that was resolved.
	KeyID string

	// Key is the key material that was returned from the URI.
	Key Key

	// Err holds any error that occurred while trying to fetch key material.
	// If this field is set, Key will be nil.
	Err error
}

ResolveEvent holds information about a key ID that has been resolved.

type ResolveListener

type ResolveListener interface {
	// OnResolveEvent receives notifications for attempts to resolve keys.  This
	// method must not panic.
	OnResolveEvent(ResolveEvent)
}

ResolveListener is a sink for ResolveEvents.

type Resolver

type Resolver interface {
	// Resolve attempts to locate a key with a given keyID (kid).
	Resolve(ctx context.Context, keyID string) (Key, error)

	// AddListener attaches a sink for ResolveEvents.  Only events that
	// occur after this method call will be dispatched to the given listener.
	AddListener(ResolveListener) CancelListenerFunc
}

Resolver allows synchronous resolution of keys.

func NewResolver

func NewResolver(options ...ResolverOption) (Resolver, error)

NewResolver constructs a Resolver from a set of options. By default, a Resolver uses the DefaultLoader() and DefaultParser().

If no URI template is supplied, this function returns ErrNoTemplate.

type ResolverOption

type ResolverOption interface {
	// contains filtered or unexported methods
}

ResolverOption represents a configurable option passed to NewResolver.

func WithKeyIDExpander

func WithKeyIDExpander(e Expander) ResolverOption

WithKeyIDExpander establishes the Expander strategy used for resolving individual keys. Callers may use this option to associate a custom Expander with a Resolver.

func WithKeyIDTemplate

func WithKeyIDTemplate(t string) ResolverOption

WithKeyIDTemplate establishes the URI template used for resolving individual keys.

func WithKeyRing

func WithKeyRing(kr KeyRing) ResolverOption

WithKeyRing sets a KeyRing to act as a cache for the Resolver. By default, a Resolver is not associated with any KeyRing.

type ResolverRefresherOption

type ResolverRefresherOption interface {
	ResolverOption
	RefresherOption
}

ResolverRefresherOption is a configurable option that applies to both a Refresher and a Resolver.

func WithConfig added in v0.0.3

func WithConfig(cfg Config) ResolverRefresherOption

WithConfig uses a Config struct to configure a Refresher and/or Resolver.

func WithFetcher

func WithFetcher(f Fetcher) ResolverRefresherOption

WithFetcher configures the Fetcher instance used by either a Resolver or a Refresher. By default, DefaultFetcher() is used.

type Thumbprinter

type Thumbprinter interface {
	// Thumbprint produces the RFC 7638 thumbprint hash, using the supplied algorithm.  The
	// typical value to pass to this method is crypto.SHA256.
	//
	// The returned byte slice contains the raw bytes of the hash.  To convert it to a string
	// conforming to RFC 7638, use base64.RawURLEncoding.EncodeToString.
	Thumbprint(crypto.Hash) ([]byte, error)
}

Thumbprinter is implemented by anything that can produce a secure thumbprint of itself.

type UnsupportedFormatError

type UnsupportedFormatError struct {
	Format string
}

UnsupportedFormatError indicates that a Parser cannot parse a given format.

func (UnsupportedFormatError) Error

func (ufe UnsupportedFormatError) Error() string

Error implements the error interface.

type UnsupportedSchemeError

type UnsupportedSchemeError struct {
	Location string
}

UnsupportedSchemeError indicates that a URI's scheme was not registered and couldn't be handled by a Loader.

func (*UnsupportedSchemeError) Error

func (use *UnsupportedSchemeError) Error() string

Directories

Path Synopsis
Package clorthofx provides integration with go.uber.org/fx.
Package clorthofx provides integration with go.uber.org/fx.
Package clorthometrics integrates clortho events with Prometheus metrics.
Package clorthometrics integrates clortho events with Prometheus metrics.
Package clorthozap provides basic integration with go.uber.org/zap.
Package clorthozap provides basic integration with go.uber.org/zap.

Jump to

Keyboard shortcuts

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