mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
Updates: - unpin AWS dependencies and run `make vendor-update` - add config options to enable checksums only if required by storage in order to preserve backwards compatibility Related issues: - https://github.com/VictoriaMetrics/VictoriaMetrics/issues/9748 - https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8622 Tested with: AWS S3, self-hosted MinIO, Linode object storage as it was failing previously with multi-part uploads (reported here - https://github.com/VictoriaMetrics/VictoriaMetrics/pull/8630#issuecomment-2772185033). An updated library allows (PR with the fix - https://github.com/aws/aws-sdk-go-v2/pull/3151) overriding multi-part upload configurations so that compatibility can be preserved. Signed-off-by: Zakhar Bessarab <[email protected]>
203 lines
5.6 KiB
Go
203 lines
5.6 KiB
Go
package jwtbundle
|
|
|
|
import (
|
|
"crypto"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/go-jose/go-jose/v4"
|
|
"github.com/spiffe/go-spiffe/v2/internal/jwtutil"
|
|
"github.com/spiffe/go-spiffe/v2/spiffeid"
|
|
)
|
|
|
|
// Bundle is a collection of trusted JWT authorities for a trust domain.
|
|
type Bundle struct {
|
|
trustDomain spiffeid.TrustDomain
|
|
|
|
mtx sync.RWMutex
|
|
jwtAuthorities map[string]crypto.PublicKey
|
|
}
|
|
|
|
// New creates a new bundle.
|
|
func New(trustDomain spiffeid.TrustDomain) *Bundle {
|
|
return &Bundle{
|
|
trustDomain: trustDomain,
|
|
jwtAuthorities: make(map[string]crypto.PublicKey),
|
|
}
|
|
}
|
|
|
|
// FromJWTAuthorities creates a new bundle from JWT authorities
|
|
func FromJWTAuthorities(trustDomain spiffeid.TrustDomain, jwtAuthorities map[string]crypto.PublicKey) *Bundle {
|
|
return &Bundle{
|
|
trustDomain: trustDomain,
|
|
jwtAuthorities: jwtutil.CopyJWTAuthorities(jwtAuthorities),
|
|
}
|
|
}
|
|
|
|
// Load loads a bundle from a file on disk. The file must contain a standard RFC 7517 JWKS document.
|
|
func Load(trustDomain spiffeid.TrustDomain, path string) (*Bundle, error) {
|
|
bundleBytes, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, wrapJwtbundleErr(fmt.Errorf("unable to read JWT bundle: %w", err))
|
|
}
|
|
|
|
return Parse(trustDomain, bundleBytes)
|
|
}
|
|
|
|
// Read decodes a bundle from a reader. The contents must contain a standard RFC 7517 JWKS document.
|
|
func Read(trustDomain spiffeid.TrustDomain, r io.Reader) (*Bundle, error) {
|
|
b, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return nil, wrapJwtbundleErr(fmt.Errorf("unable to read: %v", err))
|
|
}
|
|
|
|
return Parse(trustDomain, b)
|
|
}
|
|
|
|
// Parse parses a bundle from bytes. The data must be a standard RFC 7517 JWKS document.
|
|
func Parse(trustDomain spiffeid.TrustDomain, bundleBytes []byte) (*Bundle, error) {
|
|
jwks := new(jose.JSONWebKeySet)
|
|
if err := json.Unmarshal(bundleBytes, jwks); err != nil {
|
|
return nil, wrapJwtbundleErr(fmt.Errorf("unable to parse JWKS: %v", err))
|
|
}
|
|
|
|
bundle := New(trustDomain)
|
|
for i, key := range jwks.Keys {
|
|
if err := bundle.AddJWTAuthority(key.KeyID, key.Key); err != nil {
|
|
return nil, wrapJwtbundleErr(fmt.Errorf("error adding authority %d of JWKS: %v", i, errors.Unwrap(err)))
|
|
}
|
|
}
|
|
|
|
return bundle, nil
|
|
}
|
|
|
|
// TrustDomain returns the trust domain that the bundle belongs to.
|
|
func (b *Bundle) TrustDomain() spiffeid.TrustDomain {
|
|
return b.trustDomain
|
|
}
|
|
|
|
// JWTAuthorities returns the JWT authorities in the bundle, keyed by key ID.
|
|
func (b *Bundle) JWTAuthorities() map[string]crypto.PublicKey {
|
|
b.mtx.RLock()
|
|
defer b.mtx.RUnlock()
|
|
|
|
return jwtutil.CopyJWTAuthorities(b.jwtAuthorities)
|
|
}
|
|
|
|
// FindJWTAuthority finds the JWT authority with the given key ID from the bundle. If the authority
|
|
// is found, it is returned and the boolean is true. Otherwise, the returned
|
|
// value is nil and the boolean is false.
|
|
func (b *Bundle) FindJWTAuthority(keyID string) (crypto.PublicKey, bool) {
|
|
b.mtx.RLock()
|
|
defer b.mtx.RUnlock()
|
|
|
|
if jwtAuthority, ok := b.jwtAuthorities[keyID]; ok {
|
|
return jwtAuthority, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// HasJWTAuthority returns true if the bundle has a JWT authority with the given key ID.
|
|
func (b *Bundle) HasJWTAuthority(keyID string) bool {
|
|
b.mtx.RLock()
|
|
defer b.mtx.RUnlock()
|
|
|
|
_, ok := b.jwtAuthorities[keyID]
|
|
return ok
|
|
}
|
|
|
|
// AddJWTAuthority adds a JWT authority to the bundle. If a JWT authority already exists
|
|
// under the given key ID, it is replaced. A key ID must be specified.
|
|
func (b *Bundle) AddJWTAuthority(keyID string, jwtAuthority crypto.PublicKey) error {
|
|
if keyID == "" {
|
|
return wrapJwtbundleErr(errors.New("keyID cannot be empty"))
|
|
}
|
|
|
|
b.mtx.Lock()
|
|
defer b.mtx.Unlock()
|
|
|
|
b.jwtAuthorities[keyID] = jwtAuthority
|
|
return nil
|
|
}
|
|
|
|
// RemoveJWTAuthority removes the JWT authority identified by the key ID from the bundle.
|
|
func (b *Bundle) RemoveJWTAuthority(keyID string) {
|
|
b.mtx.Lock()
|
|
defer b.mtx.Unlock()
|
|
|
|
delete(b.jwtAuthorities, keyID)
|
|
}
|
|
|
|
// SetJWTAuthorities sets the JWT authorities in the bundle.
|
|
func (b *Bundle) SetJWTAuthorities(jwtAuthorities map[string]crypto.PublicKey) {
|
|
b.mtx.Lock()
|
|
defer b.mtx.Unlock()
|
|
|
|
b.jwtAuthorities = jwtutil.CopyJWTAuthorities(jwtAuthorities)
|
|
}
|
|
|
|
// Empty returns true if the bundle has no JWT authorities.
|
|
func (b *Bundle) Empty() bool {
|
|
b.mtx.RLock()
|
|
defer b.mtx.RUnlock()
|
|
|
|
return len(b.jwtAuthorities) == 0
|
|
}
|
|
|
|
// Marshal marshals the JWT bundle into a standard RFC 7517 JWKS document. The
|
|
// JWKS does not contain any SPIFFE-specific parameters.
|
|
func (b *Bundle) Marshal() ([]byte, error) {
|
|
b.mtx.RLock()
|
|
defer b.mtx.RUnlock()
|
|
|
|
jwks := jose.JSONWebKeySet{}
|
|
for keyID, jwtAuthority := range b.jwtAuthorities {
|
|
jwks.Keys = append(jwks.Keys, jose.JSONWebKey{
|
|
Key: jwtAuthority,
|
|
KeyID: keyID,
|
|
})
|
|
}
|
|
|
|
return json.Marshal(jwks)
|
|
}
|
|
|
|
// Clone clones the bundle.
|
|
func (b *Bundle) Clone() *Bundle {
|
|
b.mtx.RLock()
|
|
defer b.mtx.RUnlock()
|
|
|
|
return FromJWTAuthorities(b.trustDomain, b.jwtAuthorities)
|
|
}
|
|
|
|
// Equal compares the bundle for equality against the given bundle.
|
|
func (b *Bundle) Equal(other *Bundle) bool {
|
|
if b == nil || other == nil {
|
|
return b == other
|
|
}
|
|
|
|
return b.trustDomain == other.trustDomain &&
|
|
jwtutil.JWTAuthoritiesEqual(b.jwtAuthorities, other.jwtAuthorities)
|
|
}
|
|
|
|
// GetJWTBundleForTrustDomain returns the JWT bundle for the given trust
|
|
// domain. It implements the Source interface. An error will be returned if
|
|
// the trust domain does not match that of the bundle.
|
|
func (b *Bundle) GetJWTBundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) {
|
|
b.mtx.RLock()
|
|
defer b.mtx.RUnlock()
|
|
|
|
if b.trustDomain != trustDomain {
|
|
return nil, wrapJwtbundleErr(fmt.Errorf("no JWT bundle for trust domain %q", trustDomain))
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func wrapJwtbundleErr(err error) error {
|
|
return fmt.Errorf("jwtbundle: %w", err)
|
|
}
|