diff options
Diffstat (limited to 'vendor/github.com/spiffe')
25 files changed, 2566 insertions, 0 deletions
diff --git a/vendor/github.com/spiffe/go-spiffe/v2/LICENSE b/vendor/github.com/spiffe/go-spiffe/v2/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/bundle.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/bundle.go new file mode 100644 index 0000000..ebd3cac --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/bundle.go @@ -0,0 +1,200 @@ +package jwtbundle + +import ( + "crypto" + "encoding/json" + "errors" + "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" + "github.com/zeebo/errs" +) + +var jwtbundleErr = errs.Class("jwtbundle") + +// 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, jwtbundleErr.New("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, jwtbundleErr.New("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, jwtbundleErr.New("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, jwtbundleErr.New("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 jwtbundleErr.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, jwtbundleErr.New("no JWT bundle for trust domain %q", trustDomain) + } + + return b, nil +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/doc.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/doc.go new file mode 100644 index 0000000..394878e --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/doc.go @@ -0,0 +1,43 @@ +// Package jwtbundle provides JWT bundle related functionality. +// +// A bundle represents a collection of JWT authorities, i.e., those that +// are used to authenticate SPIFFE JWT-SVIDs. +// +// You can create a new bundle for a specific trust domain: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// bundle := jwtbundle.New(td) +// +// Or you can load it from disk: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// bundle := jwtbundle.Load(td, "bundle.jwks") +// +// The bundle can be initialized with JWT authorities: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// var jwtAuthorities map[string]crypto.PublicKey = ... +// bundle := jwtbundle.FromJWTAuthorities(td, jwtAuthorities) +// +// In addition, you can add JWT authorities to the bundle: +// +// var keyID string = ... +// var publicKey crypto.PublicKey = ... +// bundle.AddJWTAuthority(keyID, publicKey) +// +// Bundles can be organized into a set, keyed by trust domain: +// +// set := jwtbundle.NewSet() +// set.Add(bundle) +// +// A Source is source of JWT bundles for a trust domain. Both the Bundle +// and Set types implement Source: +// +// // Initialize the source from a bundle or set +// var source jwtbundle.Source = bundle +// // ... or ... +// var source jwtbundle.Source = set +// +// // Use the source to query for bundles by trust domain +// bundle, err := source.GetJWTBundleForTrustDomain(td) +package jwtbundle diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/set.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/set.go new file mode 100644 index 0000000..048dd0d --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/set.go @@ -0,0 +1,105 @@ +package jwtbundle + +import ( + "sort" + "sync" + + "github.com/spiffe/go-spiffe/v2/spiffeid" +) + +// Set is a set of bundles, keyed by trust domain. +type Set struct { + mtx sync.RWMutex + bundles map[spiffeid.TrustDomain]*Bundle +} + +// NewSet creates a new set initialized with the given bundles. +func NewSet(bundles ...*Bundle) *Set { + bundlesMap := make(map[spiffeid.TrustDomain]*Bundle) + + for _, b := range bundles { + if b != nil { + bundlesMap[b.trustDomain] = b + } + } + + return &Set{ + bundles: bundlesMap, + } +} + +// Add adds a new bundle into the set. If a bundle already exists for the +// trust domain, the existing bundle is replaced. +func (s *Set) Add(bundle *Bundle) { + s.mtx.Lock() + defer s.mtx.Unlock() + + if bundle != nil { + s.bundles[bundle.trustDomain] = bundle + } +} + +// Remove removes the bundle for the given trust domain. +func (s *Set) Remove(trustDomain spiffeid.TrustDomain) { + s.mtx.Lock() + defer s.mtx.Unlock() + + delete(s.bundles, trustDomain) +} + +// Has returns true if there is a bundle for the given trust domain. +func (s *Set) Has(trustDomain spiffeid.TrustDomain) bool { + s.mtx.RLock() + defer s.mtx.RUnlock() + + _, ok := s.bundles[trustDomain] + return ok +} + +// Get returns a bundle for the given trust domain. If the bundle is in the set +// it is returned and the boolean is true. Otherwise, the returned value is +// nil and the boolean is false. +func (s *Set) Get(trustDomain spiffeid.TrustDomain) (*Bundle, bool) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + bundle, ok := s.bundles[trustDomain] + return bundle, ok +} + +// Bundles returns the bundles in the set sorted by trust domain. +func (s *Set) Bundles() []*Bundle { + s.mtx.RLock() + defer s.mtx.RUnlock() + + out := make([]*Bundle, 0, len(s.bundles)) + for _, bundle := range s.bundles { + out = append(out, bundle) + } + sort.Slice(out, func(a, b int) bool { + return out[a].TrustDomain().Compare(out[b].TrustDomain()) < 0 + }) + return out +} + +// Len returns the number of bundles in the set. +func (s *Set) Len() int { + s.mtx.RLock() + defer s.mtx.RUnlock() + + return len(s.bundles) +} + +// GetJWTBundleForTrustDomain returns the JWT bundle for the given trust +// domain. It implements the Source interface. +func (s *Set) GetJWTBundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + bundle, ok := s.bundles[trustDomain] + if !ok { + return nil, jwtbundleErr.New("no JWT bundle for trust domain %q", trustDomain) + } + + return bundle, nil +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/source.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/source.go new file mode 100644 index 0000000..224cd9f --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/source.go @@ -0,0 +1,12 @@ +package jwtbundle + +import ( + "github.com/spiffe/go-spiffe/v2/spiffeid" +) + +// Source represents a source of JWT bundles keyed by trust domain. +type Source interface { + // GetJWTBundleForTrustDomain returns the JWT bundle for the given trust + // domain. + GetJWTBundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/bundle.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/bundle.go new file mode 100644 index 0000000..13b103e --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/bundle.go @@ -0,0 +1,485 @@ +package spiffebundle + +import ( + "crypto" + "crypto/x509" + "encoding/json" + "errors" + "io" + "os" + "sync" + "time" + + "github.com/go-jose/go-jose/v4" + "github.com/spiffe/go-spiffe/v2/bundle/jwtbundle" + "github.com/spiffe/go-spiffe/v2/bundle/x509bundle" + "github.com/spiffe/go-spiffe/v2/internal/jwtutil" + "github.com/spiffe/go-spiffe/v2/internal/x509util" + "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/zeebo/errs" +) + +const ( + x509SVIDUse = "x509-svid" + jwtSVIDUse = "jwt-svid" +) + +var spiffebundleErr = errs.Class("spiffebundle") + +type bundleDoc struct { + jose.JSONWebKeySet + SequenceNumber *uint64 `json:"spiffe_sequence,omitempty"` + RefreshHint *int64 `json:"spiffe_refresh_hint,omitempty"` +} + +// Bundle is a collection of trusted public key material for a trust domain, +// conforming to the SPIFFE Bundle Format as part of the SPIFFE Trust Domain +// and Bundle specification: +// https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md +type Bundle struct { + trustDomain spiffeid.TrustDomain + + mtx sync.RWMutex + refreshHint *time.Duration + sequenceNumber *uint64 + jwtAuthorities map[string]crypto.PublicKey + x509Authorities []*x509.Certificate +} + +// New creates a new bundle. +func New(trustDomain spiffeid.TrustDomain) *Bundle { + return &Bundle{ + trustDomain: trustDomain, + jwtAuthorities: make(map[string]crypto.PublicKey), + } +} + +// Load loads a bundle from a file on disk. The file must contain a JWKS +// document following the SPIFFE Trust Domain and Bundle specification. +func Load(trustDomain spiffeid.TrustDomain, path string) (*Bundle, error) { + bundleBytes, err := os.ReadFile(path) + if err != nil { + return nil, spiffebundleErr.New("unable to read SPIFFE bundle: %w", err) + } + + return Parse(trustDomain, bundleBytes) +} + +// Read decodes a bundle from a reader. The contents must contain a JWKS +// document following the SPIFFE Trust Domain and Bundle specification. +func Read(trustDomain spiffeid.TrustDomain, r io.Reader) (*Bundle, error) { + b, err := io.ReadAll(r) + if err != nil { + return nil, spiffebundleErr.New("unable to read: %v", err) + } + + return Parse(trustDomain, b) +} + +// Parse parses a bundle from bytes. The data must be a JWKS document following +// the SPIFFE Trust Domain and Bundle specification. +func Parse(trustDomain spiffeid.TrustDomain, bundleBytes []byte) (*Bundle, error) { + jwks := &bundleDoc{} + if err := json.Unmarshal(bundleBytes, jwks); err != nil { + return nil, spiffebundleErr.New("unable to parse JWKS: %v", err) + } + + bundle := New(trustDomain) + if jwks.RefreshHint != nil { + bundle.SetRefreshHint(time.Second * time.Duration(*jwks.RefreshHint)) + } + if jwks.SequenceNumber != nil { + bundle.SetSequenceNumber(*jwks.SequenceNumber) + } + + if jwks.Keys == nil { + // The parameter keys MUST be present. + // https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#413-keys + return nil, spiffebundleErr.New("no authorities found") + } + for i, key := range jwks.Keys { + switch key.Use { + // Two SVID types are supported: x509-svid and jwt-svid. + case x509SVIDUse: + if len(key.Certificates) != 1 { + return nil, spiffebundleErr.New("expected a single certificate in %s entry %d; got %d", x509SVIDUse, i, len(key.Certificates)) + } + bundle.AddX509Authority(key.Certificates[0]) + case jwtSVIDUse: + if err := bundle.AddJWTAuthority(key.KeyID, key.Key); err != nil { + return nil, spiffebundleErr.New("error adding authority %d of JWKS: %v", i, errors.Unwrap(err)) + } + } + } + + return bundle, nil +} + +// FromX509Bundle creates a bundle from an X.509 bundle. +// The function panics in case of a nil X.509 bundle. +func FromX509Bundle(x509Bundle *x509bundle.Bundle) *Bundle { + bundle := New(x509Bundle.TrustDomain()) + bundle.x509Authorities = x509Bundle.X509Authorities() + return bundle +} + +// FromJWTBundle creates a bundle from a JWT bundle. +// The function panics in case of a nil JWT bundle. +func FromJWTBundle(jwtBundle *jwtbundle.Bundle) *Bundle { + bundle := New(jwtBundle.TrustDomain()) + bundle.jwtAuthorities = jwtBundle.JWTAuthorities() + return bundle +} + +// FromX509Authorities creates a bundle from X.509 certificates. +func FromX509Authorities(trustDomain spiffeid.TrustDomain, x509Authorities []*x509.Certificate) *Bundle { + bundle := New(trustDomain) + bundle.x509Authorities = x509util.CopyX509Authorities(x509Authorities) + return bundle +} + +// FromJWTAuthorities creates a new bundle from JWT authorities. +func FromJWTAuthorities(trustDomain spiffeid.TrustDomain, jwtAuthorities map[string]crypto.PublicKey) *Bundle { + bundle := New(trustDomain) + bundle.jwtAuthorities = jwtutil.CopyJWTAuthorities(jwtAuthorities) + return bundle +} + +// TrustDomain returns the trust domain that the bundle belongs to. +func (b *Bundle) TrustDomain() spiffeid.TrustDomain { + return b.trustDomain +} + +// X509Authorities returns the X.509 authorities in the bundle. +func (b *Bundle) X509Authorities() []*x509.Certificate { + b.mtx.RLock() + defer b.mtx.RUnlock() + + return x509util.CopyX509Authorities(b.x509Authorities) +} + +// AddX509Authority adds an X.509 authority to the bundle. If the authority already +// exists in the bundle, the contents of the bundle will remain unchanged. +func (b *Bundle) AddX509Authority(x509Authority *x509.Certificate) { + b.mtx.Lock() + defer b.mtx.Unlock() + + for _, r := range b.x509Authorities { + if r.Equal(x509Authority) { + return + } + } + + b.x509Authorities = append(b.x509Authorities, x509Authority) +} + +// RemoveX509Authority removes an X.509 authority from the bundle. +func (b *Bundle) RemoveX509Authority(x509Authority *x509.Certificate) { + b.mtx.Lock() + defer b.mtx.Unlock() + + for i, r := range b.x509Authorities { + if r.Equal(x509Authority) { + b.x509Authorities = append(b.x509Authorities[:i], b.x509Authorities[i+1:]...) + return + } + } +} + +// HasX509Authority checks if the given X.509 authority exists in the bundle. +func (b *Bundle) HasX509Authority(x509Authority *x509.Certificate) bool { + b.mtx.RLock() + defer b.mtx.RUnlock() + + for _, r := range b.x509Authorities { + if r.Equal(x509Authority) { + return true + } + } + return false +} + +// SetX509Authorities sets the X.509 authorities in the bundle. +func (b *Bundle) SetX509Authorities(authorities []*x509.Certificate) { + b.mtx.Lock() + defer b.mtx.Unlock() + + b.x509Authorities = x509util.CopyX509Authorities(authorities) +} + +// 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() + + jwtAuthority, ok := b.jwtAuthorities[keyID] + return jwtAuthority, ok +} + +// 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 spiffebundleErr.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 X.509 and JWT authorities. +func (b *Bundle) Empty() bool { + b.mtx.RLock() + defer b.mtx.RUnlock() + + return len(b.x509Authorities) == 0 && len(b.jwtAuthorities) == 0 +} + +// RefreshHint returns the refresh hint. If the refresh hint is set in +// the bundle, it is returned and the boolean is true. Otherwise, the returned +// value is zero and the boolean is false. +func (b *Bundle) RefreshHint() (refreshHint time.Duration, ok bool) { + b.mtx.RLock() + defer b.mtx.RUnlock() + + if b.refreshHint != nil { + return *b.refreshHint, true + } + return 0, false +} + +// SetRefreshHint sets the refresh hint. The refresh hint value will be +// truncated to time.Second. +func (b *Bundle) SetRefreshHint(refreshHint time.Duration) { + b.mtx.Lock() + defer b.mtx.Unlock() + + b.refreshHint = &refreshHint +} + +// ClearRefreshHint clears the refresh hint. +func (b *Bundle) ClearRefreshHint() { + b.mtx.Lock() + defer b.mtx.Unlock() + + b.refreshHint = nil +} + +// SequenceNumber returns the sequence number. If the sequence number is set in +// the bundle, it is returned and the boolean is true. Otherwise, the returned +// value is zero and the boolean is false. +func (b *Bundle) SequenceNumber() (uint64, bool) { + b.mtx.RLock() + defer b.mtx.RUnlock() + + if b.sequenceNumber != nil { + return *b.sequenceNumber, true + } + return 0, false +} + +// SetSequenceNumber sets the sequence number. +func (b *Bundle) SetSequenceNumber(sequenceNumber uint64) { + b.mtx.Lock() + defer b.mtx.Unlock() + + b.sequenceNumber = &sequenceNumber +} + +// ClearSequenceNumber clears the sequence number. +func (b *Bundle) ClearSequenceNumber() { + b.mtx.Lock() + defer b.mtx.Unlock() + + b.sequenceNumber = nil +} + +// Marshal marshals the bundle according to the SPIFFE Trust Domain and Bundle +// specification. The trust domain is not marshaled as part of the bundle and +// must be conveyed separately. See the specification for details. +func (b *Bundle) Marshal() ([]byte, error) { + b.mtx.RLock() + defer b.mtx.RUnlock() + + jwks := bundleDoc{} + if b.refreshHint != nil { + tr := int64((*b.refreshHint + (time.Second - 1)) / time.Second) + jwks.RefreshHint = &tr + } + jwks.SequenceNumber = b.sequenceNumber + for _, x509Authority := range b.x509Authorities { + jwks.Keys = append(jwks.Keys, jose.JSONWebKey{ + Key: x509Authority.PublicKey, + Certificates: []*x509.Certificate{x509Authority}, + Use: x509SVIDUse, + }) + } + + for keyID, jwtAuthority := range b.jwtAuthorities { + jwks.Keys = append(jwks.Keys, jose.JSONWebKey{ + Key: jwtAuthority, + KeyID: keyID, + Use: jwtSVIDUse, + }) + } + + return json.Marshal(jwks) +} + +// Clone clones the bundle. +func (b *Bundle) Clone() *Bundle { + b.mtx.RLock() + defer b.mtx.RUnlock() + + return &Bundle{ + trustDomain: b.trustDomain, + refreshHint: copyRefreshHint(b.refreshHint), + sequenceNumber: copySequenceNumber(b.sequenceNumber), + x509Authorities: x509util.CopyX509Authorities(b.x509Authorities), + jwtAuthorities: jwtutil.CopyJWTAuthorities(b.jwtAuthorities), + } +} + +// X509Bundle returns an X.509 bundle containing the X.509 authorities in the SPIFFE +// bundle. +func (b *Bundle) X509Bundle() *x509bundle.Bundle { + b.mtx.RLock() + defer b.mtx.RUnlock() + + // FromX509Authorities makes a copy, so we can pass our internal slice directly. + return x509bundle.FromX509Authorities(b.trustDomain, b.x509Authorities) +} + +// JWTBundle returns a JWT bundle containing the JWT authorities in the SPIFFE bundle. +func (b *Bundle) JWTBundle() *jwtbundle.Bundle { + b.mtx.RLock() + defer b.mtx.RUnlock() + + // FromJWTBundle makes a copy, so we can pass our internal slice directly. + return jwtbundle.FromJWTAuthorities(b.trustDomain, b.jwtAuthorities) +} + +// GetBundleForTrustDomain returns the SPIFFE 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) GetBundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) { + b.mtx.RLock() + defer b.mtx.RUnlock() + + if b.trustDomain != trustDomain { + return nil, spiffebundleErr.New("no SPIFFE bundle for trust domain %q", trustDomain) + } + + return b, nil +} + +// GetX509BundleForTrustDomain returns the X.509 bundle for the given trust +// domain. It implements the x509bundle.Source interface. An error will be +// returned if the trust domain does not match that of the bundle. +func (b *Bundle) GetX509BundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*x509bundle.Bundle, error) { + b.mtx.RLock() + defer b.mtx.RUnlock() + + if b.trustDomain != trustDomain { + return nil, spiffebundleErr.New("no X.509 bundle for trust domain %q", trustDomain) + } + + return b.X509Bundle(), nil +} + +// GetJWTBundleForTrustDomain returns the JWT bundle of the given trust domain. +// It implements the jwtbundle.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) (*jwtbundle.Bundle, error) { + b.mtx.RLock() + defer b.mtx.RUnlock() + + if b.trustDomain != trustDomain { + return nil, spiffebundleErr.New("no JWT bundle for trust domain %q", trustDomain) + } + + return b.JWTBundle(), nil +} + +// 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 && + refreshHintEqual(b.refreshHint, other.refreshHint) && + sequenceNumberEqual(b.sequenceNumber, other.sequenceNumber) && + jwtutil.JWTAuthoritiesEqual(b.jwtAuthorities, other.jwtAuthorities) && + x509util.CertsEqual(b.x509Authorities, other.x509Authorities) +} + +func refreshHintEqual(a, b *time.Duration) bool { + if a == nil || b == nil { + return a == b + } + + return *a == *b +} + +func sequenceNumberEqual(a, b *uint64) bool { + if a == nil || b == nil { + return a == b + } + + return *a == *b +} + +func copyRefreshHint(refreshHint *time.Duration) *time.Duration { + if refreshHint == nil { + return nil + } + copied := *refreshHint + return &copied +} + +func copySequenceNumber(sequenceNumber *uint64) *uint64 { + if sequenceNumber == nil { + return nil + } + copied := *sequenceNumber + return &copied +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/doc.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/doc.go new file mode 100644 index 0000000..db9dcde --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/doc.go @@ -0,0 +1,59 @@ +// Package spiffebundle provides SPIFFE bundle related functionality. +// +// A bundle represents a SPIFFE bundle, a collection authorities for +// authenticating SVIDs. +// +// You can create a new bundle for a specific trust domain: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// bundle := spiffebundle.New(td) +// +// Or you can load it from disk: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// bundle := spiffebundle.Load(td, "bundle.json") +// +// The bundle can be initialized with X.509 or JWT authorities: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// +// var x509Authorities []*x509.Certificate = ... +// bundle := spiffebundle.FromX509Authorities(td, x509Authorities) +// // ... or ... +// var jwtAuthorities map[string]crypto.PublicKey = ... +// bundle := spiffebundle.FromJWTAuthorities(td, jwtAuthorities) +// +// In addition, you can add authorities to the bundle: +// +// var x509CA *x509.Certificate = ... +// bundle.AddX509Authority(x509CA) +// var keyID string = ... +// var publicKey crypto.PublicKey = ... +// bundle.AddJWTAuthority(keyID, publicKey) +// +// Bundles can be organized into a set, keyed by trust domain: +// +// set := spiffebundle.NewSet() +// set.Add(bundle) +// +// A Source is source of bundles for a trust domain. Both the +// Bundle and Set types implement Source: +// +// // Initialize the source from a bundle or set +// var source spiffebundle.Source = bundle +// // ... or ... +// var source spiffebundle.Source = set +// +// // Use the source to query for X.509 bundles by trust domain +// bundle, err := source.GetBundleForTrustDomain(td) +// +// Additionally the Bundle and Set types also implement the x509bundle.Source and jwtbundle.Source interfaces: +// +// // As an x509bundle.Source... +// var source x509bundle.Source = bundle // or set +// x509Bundle, err := source.GetX509BundleForTrustDomain(td) +// +// // As a jwtbundle.Source... +// var source jwtbundle.Source = bundle // or set +// jwtBundle, err := source.GetJWTBundleForTrustDomain(td) +package spiffebundle diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/set.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/set.go new file mode 100644 index 0000000..2738135 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/set.go @@ -0,0 +1,135 @@ +package spiffebundle + +import ( + "sort" + "sync" + + "github.com/spiffe/go-spiffe/v2/bundle/jwtbundle" + "github.com/spiffe/go-spiffe/v2/bundle/x509bundle" + "github.com/spiffe/go-spiffe/v2/spiffeid" +) + +// Set is a set of bundles, keyed by trust domain. +type Set struct { + mtx sync.RWMutex + bundles map[spiffeid.TrustDomain]*Bundle +} + +// NewSet creates a new set initialized with the given bundles. +func NewSet(bundles ...*Bundle) *Set { + bundlesMap := make(map[spiffeid.TrustDomain]*Bundle) + + for _, b := range bundles { + if b != nil { + bundlesMap[b.trustDomain] = b + } + } + + return &Set{ + bundles: bundlesMap, + } +} + +// Add adds a new bundle into the set. If a bundle already exists for the +// trust domain, the existing bundle is replaced. +func (s *Set) Add(bundle *Bundle) { + s.mtx.Lock() + defer s.mtx.Unlock() + + if bundle != nil { + s.bundles[bundle.trustDomain] = bundle + } +} + +// Remove removes the bundle for the given trust domain. +func (s *Set) Remove(trustDomain spiffeid.TrustDomain) { + s.mtx.Lock() + defer s.mtx.Unlock() + + delete(s.bundles, trustDomain) +} + +// Has returns true if there is a bundle for the given trust domain. +func (s *Set) Has(trustDomain spiffeid.TrustDomain) bool { + s.mtx.RLock() + defer s.mtx.RUnlock() + + _, ok := s.bundles[trustDomain] + return ok +} + +// Get returns a bundle for the given trust domain. If the bundle is in the set +// it is returned and the boolean is true. Otherwise, the returned value is +// nil and the boolean is false. +func (s *Set) Get(trustDomain spiffeid.TrustDomain) (*Bundle, bool) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + bundle, ok := s.bundles[trustDomain] + return bundle, ok +} + +// Bundles returns the bundles in the set sorted by trust domain. +func (s *Set) Bundles() []*Bundle { + s.mtx.RLock() + defer s.mtx.RUnlock() + + out := make([]*Bundle, 0, len(s.bundles)) + for _, bundle := range s.bundles { + out = append(out, bundle) + } + sort.Slice(out, func(a, b int) bool { + return out[a].TrustDomain().Compare(out[b].TrustDomain()) < 0 + }) + return out +} + +// Len returns the number of bundles in the set. +func (s *Set) Len() int { + s.mtx.RLock() + defer s.mtx.RUnlock() + + return len(s.bundles) +} + +// GetBundleForTrustDomain returns the SPIFFE bundle for the given trust +// domain. It implements the Source interface. +func (s *Set) GetBundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + bundle, ok := s.bundles[trustDomain] + if !ok { + return nil, spiffebundleErr.New("no SPIFFE bundle for trust domain %q", trustDomain) + } + + return bundle, nil +} + +// GetX509BundleForTrustDomain returns the X.509 bundle for the given trust +// domain. It implements the x509bundle.Source interface. +func (s *Set) GetX509BundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*x509bundle.Bundle, error) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + bundle, ok := s.bundles[trustDomain] + if !ok { + return nil, spiffebundleErr.New("no X.509 bundle for trust domain %q", trustDomain) + } + + return bundle.X509Bundle(), nil +} + +// GetJWTBundleForTrustDomain returns the JWT bundle for the given trust +// domain. It implements the jwtbundle.Source interface. +func (s *Set) GetJWTBundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*jwtbundle.Bundle, error) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + bundle, ok := s.bundles[trustDomain] + if !ok { + return nil, spiffebundleErr.New("no JWT bundle for trust domain %q", trustDomain) + } + + return bundle.JWTBundle(), nil +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/source.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/source.go new file mode 100644 index 0000000..f4d3712 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/source.go @@ -0,0 +1,10 @@ +package spiffebundle + +import "github.com/spiffe/go-spiffe/v2/spiffeid" + +// Source represents a source of SPIFFE bundles keyed by trust domain. +type Source interface { + // GetBundleForTrustDomain returns the SPIFFE bundle for the given trust + // domain. + GetBundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/bundle.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/bundle.go new file mode 100644 index 0000000..a70bb62 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/bundle.go @@ -0,0 +1,202 @@ +package x509bundle + +import ( + "crypto/x509" + "io" + "os" + "sync" + + "github.com/spiffe/go-spiffe/v2/internal/pemutil" + "github.com/spiffe/go-spiffe/v2/internal/x509util" + "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/zeebo/errs" +) + +var x509bundleErr = errs.Class("x509bundle") + +// Bundle is a collection of trusted X.509 authorities for a trust domain. +type Bundle struct { + trustDomain spiffeid.TrustDomain + + mtx sync.RWMutex + x509Authorities []*x509.Certificate +} + +// New creates a new bundle. +func New(trustDomain spiffeid.TrustDomain) *Bundle { + return &Bundle{ + trustDomain: trustDomain, + } +} + +// FromX509Authorities creates a bundle from X.509 certificates. +func FromX509Authorities(trustDomain spiffeid.TrustDomain, authorities []*x509.Certificate) *Bundle { + return &Bundle{ + trustDomain: trustDomain, + x509Authorities: x509util.CopyX509Authorities(authorities), + } +} + +// Load loads a bundle from a file on disk. The file must contain PEM-encoded +// certificate blocks. +func Load(trustDomain spiffeid.TrustDomain, path string) (*Bundle, error) { + fileBytes, err := os.ReadFile(path) + if err != nil { + return nil, x509bundleErr.New("unable to load X.509 bundle file: %w", err) + } + + return Parse(trustDomain, fileBytes) +} + +// Read decodes a bundle from a reader. The contents must be PEM-encoded +// certificate blocks. +func Read(trustDomain spiffeid.TrustDomain, r io.Reader) (*Bundle, error) { + b, err := io.ReadAll(r) + if err != nil { + return nil, x509bundleErr.New("unable to read X.509 bundle: %v", err) + } + + return Parse(trustDomain, b) +} + +// Parse parses a bundle from bytes. The data must be PEM-encoded certificate +// blocks. +func Parse(trustDomain spiffeid.TrustDomain, b []byte) (*Bundle, error) { + bundle := New(trustDomain) + if len(b) == 0 { + return bundle, nil + } + + certs, err := pemutil.ParseCertificates(b) + if err != nil { + return nil, x509bundleErr.New("cannot parse certificate: %v", err) + } + for _, cert := range certs { + bundle.AddX509Authority(cert) + } + return bundle, nil +} + +// ParseRaw parses a bundle from bytes. The certificate must be ASN.1 DER (concatenated +// with no intermediate padding if there are more than one certificate) +func ParseRaw(trustDomain spiffeid.TrustDomain, b []byte) (*Bundle, error) { + bundle := New(trustDomain) + if len(b) == 0 { + return bundle, nil + } + + certs, err := x509.ParseCertificates(b) + if err != nil { + return nil, x509bundleErr.New("cannot parse certificate: %v", err) + } + for _, cert := range certs { + bundle.AddX509Authority(cert) + } + return bundle, nil +} + +// TrustDomain returns the trust domain that the bundle belongs to. +func (b *Bundle) TrustDomain() spiffeid.TrustDomain { + return b.trustDomain +} + +// X509Authorities returns the X.509 x509Authorities in the bundle. +func (b *Bundle) X509Authorities() []*x509.Certificate { + b.mtx.RLock() + defer b.mtx.RUnlock() + return x509util.CopyX509Authorities(b.x509Authorities) +} + +// AddX509Authority adds an X.509 authority to the bundle. If the authority already +// exists in the bundle, the contents of the bundle will remain unchanged. +func (b *Bundle) AddX509Authority(x509Authority *x509.Certificate) { + b.mtx.Lock() + defer b.mtx.Unlock() + + for _, r := range b.x509Authorities { + if r.Equal(x509Authority) { + return + } + } + + b.x509Authorities = append(b.x509Authorities, x509Authority) +} + +// RemoveX509Authority removes an X.509 authority from the bundle. +func (b *Bundle) RemoveX509Authority(x509Authority *x509.Certificate) { + b.mtx.Lock() + defer b.mtx.Unlock() + + for i, r := range b.x509Authorities { + if r.Equal(x509Authority) { + // remove element from slice + b.x509Authorities = append(b.x509Authorities[:i], b.x509Authorities[i+1:]...) + return + } + } +} + +// HasX509Authority checks if the given X.509 authority exists in the bundle. +func (b *Bundle) HasX509Authority(x509Authority *x509.Certificate) bool { + b.mtx.RLock() + defer b.mtx.RUnlock() + + for _, r := range b.x509Authorities { + if r.Equal(x509Authority) { + return true + } + } + return false +} + +// SetX509Authorities sets the X.509 authorities in the bundle. +func (b *Bundle) SetX509Authorities(x509Authorities []*x509.Certificate) { + b.mtx.Lock() + defer b.mtx.Unlock() + + b.x509Authorities = x509util.CopyX509Authorities(x509Authorities) +} + +// Empty returns true if the bundle has no X.509 x509Authorities. +func (b *Bundle) Empty() bool { + b.mtx.RLock() + defer b.mtx.RUnlock() + + return len(b.x509Authorities) == 0 +} + +// Marshal marshals the X.509 bundle into PEM-encoded certificate blocks. +func (b *Bundle) Marshal() ([]byte, error) { + b.mtx.RLock() + defer b.mtx.RUnlock() + return pemutil.EncodeCertificates(b.x509Authorities), nil +} + +// 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 && + x509util.CertsEqual(b.x509Authorities, other.x509Authorities) +} + +// Clone clones the bundle. +func (b *Bundle) Clone() *Bundle { + b.mtx.RLock() + defer b.mtx.RUnlock() + + return FromX509Authorities(b.trustDomain, b.x509Authorities) +} + +// GetX509BundleForTrustDomain returns the X.509 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) GetX509BundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) { + if b.trustDomain != trustDomain { + return nil, x509bundleErr.New("no X.509 bundle found for trust domain: %q", trustDomain) + } + + return b, nil +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/doc.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/doc.go new file mode 100644 index 0000000..889554f --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/doc.go @@ -0,0 +1,42 @@ +// Package x509bundle provides X.509 bundle related functionality. +// +// A bundle represents a collection of X.509 authorities, i.e., those that +// are used to authenticate SPIFFE X509-SVIDs. +// +// You can create a new bundle for a specific trust domain: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// bundle := x509bundle.New(td) +// +// Or you can load it from disk: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// bundle := x509bundle.Load(td, "bundle.pem") +// +// The bundle can be initialized with X.509 authorities: +// +// td := spiffeid.RequireTrustDomainFromString("example.org") +// var x509Authorities []*x509.Certificate = ... +// bundle := x509bundle.FromX509Authorities(td, x509Authorities) +// +// In addition, you can add X.509 authorities to the bundle: +// +// var x509CA *x509.Certificate = ... +// bundle.AddX509Authority(x509CA) +// +// Bundles can be organized into a set, keyed by trust domain: +// +// set := x509bundle.NewSet() +// set.Add(bundle) +// +// A Source is source of X.509 bundles for a trust domain. Both the Bundle +// and Set types implement Source: +// +// // Initialize the source from a bundle or set +// var source x509bundle.Source = bundle +// // ... or ... +// var source x509bundle.Source = set +// +// // Use the source to query for bundles by trust domain +// bundle, err := source.GetX509BundleForTrustDomain(td) +package x509bundle diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/set.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/set.go new file mode 100644 index 0000000..522e249 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/set.go @@ -0,0 +1,105 @@ +package x509bundle + +import ( + "sort" + "sync" + + "github.com/spiffe/go-spiffe/v2/spiffeid" +) + +// Set is a set of bundles, keyed by trust domain. +type Set struct { + mtx sync.RWMutex + bundles map[spiffeid.TrustDomain]*Bundle +} + +// NewSet creates a new set initialized with the given bundles. +func NewSet(bundles ...*Bundle) *Set { + bundlesMap := make(map[spiffeid.TrustDomain]*Bundle) + + for _, b := range bundles { + if b != nil { + bundlesMap[b.trustDomain] = b + } + } + + return &Set{ + bundles: bundlesMap, + } +} + +// Add adds a new bundle into the set. If a bundle already exists for the +// trust domain, the existing bundle is replaced. +func (s *Set) Add(bundle *Bundle) { + s.mtx.Lock() + defer s.mtx.Unlock() + + if bundle != nil { + s.bundles[bundle.trustDomain] = bundle + } +} + +// Remove removes the bundle for the given trust domain. +func (s *Set) Remove(trustDomain spiffeid.TrustDomain) { + s.mtx.Lock() + defer s.mtx.Unlock() + + delete(s.bundles, trustDomain) +} + +// Has returns true if there is a bundle for the given trust domain. +func (s *Set) Has(trustDomain spiffeid.TrustDomain) bool { + s.mtx.RLock() + defer s.mtx.RUnlock() + + _, ok := s.bundles[trustDomain] + return ok +} + +// Get returns a bundle for the given trust domain. If the bundle is in the set +// it is returned and the boolean is true. Otherwise, the returned value is +// nil and the boolean is false. +func (s *Set) Get(trustDomain spiffeid.TrustDomain) (*Bundle, bool) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + bundle, ok := s.bundles[trustDomain] + return bundle, ok +} + +// Bundles returns the bundles in the set sorted by trust domain. +func (s *Set) Bundles() []*Bundle { + s.mtx.RLock() + defer s.mtx.RUnlock() + + out := make([]*Bundle, 0, len(s.bundles)) + for _, bundle := range s.bundles { + out = append(out, bundle) + } + sort.Slice(out, func(a, b int) bool { + return out[a].TrustDomain().Compare(out[b].TrustDomain()) < 0 + }) + return out +} + +// Len returns the number of bundles in the set. +func (s *Set) Len() int { + s.mtx.RLock() + defer s.mtx.RUnlock() + + return len(s.bundles) +} + +// GetX509BundleForTrustDomain returns the X.509 bundle for the given trust +// domain. It implements the Source interface. +func (s *Set) GetX509BundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) { + s.mtx.RLock() + defer s.mtx.RUnlock() + + bundle, ok := s.bundles[trustDomain] + if !ok { + return nil, x509bundleErr.New("no X.509 bundle for trust domain %q", trustDomain) + } + + return bundle, nil +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/source.go b/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/source.go new file mode 100644 index 0000000..2244635 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/source.go @@ -0,0 +1,12 @@ +package x509bundle + +import ( + "github.com/spiffe/go-spiffe/v2/spiffeid" +) + +// Source represents a source of X.509 bundles keyed by trust domain. +type Source interface { + // GetX509BundleForTrustDomain returns the X.509 bundle for the given trust + // domain. + GetX509BundleForTrustDomain(trustDomain spiffeid.TrustDomain) (*Bundle, error) +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/internal/cryptoutil/keys.go b/vendor/github.com/spiffe/go-spiffe/v2/internal/cryptoutil/keys.go new file mode 100644 index 0000000..8e4e210 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/internal/cryptoutil/keys.go @@ -0,0 +1,34 @@ +package cryptoutil + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "fmt" +) + +func PublicKeyEqual(a, b crypto.PublicKey) (bool, error) { + switch a := a.(type) { + case *rsa.PublicKey: + rsaPublicKey, ok := b.(*rsa.PublicKey) + return ok && RSAPublicKeyEqual(a, rsaPublicKey), nil + case *ecdsa.PublicKey: + ecdsaPublicKey, ok := b.(*ecdsa.PublicKey) + return ok && ECDSAPublicKeyEqual(a, ecdsaPublicKey), nil + case ed25519.PublicKey: + ed25519PublicKey, ok := b.(ed25519.PublicKey) + return ok && bytes.Equal(a, ed25519PublicKey), nil + default: + return false, fmt.Errorf("unsupported public key type %T", a) + } +} + +func RSAPublicKeyEqual(a, b *rsa.PublicKey) bool { + return a.E == b.E && a.N.Cmp(b.N) == 0 +} + +func ECDSAPublicKeyEqual(a, b *ecdsa.PublicKey) bool { + return a.Curve == b.Curve && a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0 +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/internal/jwtutil/util.go b/vendor/github.com/spiffe/go-spiffe/v2/internal/jwtutil/util.go new file mode 100644 index 0000000..8605279 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/internal/jwtutil/util.go @@ -0,0 +1,34 @@ +package jwtutil + +import ( + "crypto" + + "github.com/spiffe/go-spiffe/v2/internal/cryptoutil" +) + +// CopyJWTAuthorities copies JWT authorities from a map to a new map. +func CopyJWTAuthorities(jwtAuthorities map[string]crypto.PublicKey) map[string]crypto.PublicKey { + copiedJWTAuthorities := make(map[string]crypto.PublicKey) + for key, jwtAuthority := range jwtAuthorities { + copiedJWTAuthorities[key] = jwtAuthority + } + return copiedJWTAuthorities +} + +func JWTAuthoritiesEqual(a, b map[string]crypto.PublicKey) bool { + if len(a) != len(b) { + return false + } + + for k, pka := range a { + pkb, ok := b[k] + if !ok { + return false + } + if equal, _ := cryptoutil.PublicKeyEqual(pka, pkb); !equal { + return false + } + } + + return true +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/internal/pemutil/pem.go b/vendor/github.com/spiffe/go-spiffe/v2/internal/pemutil/pem.go new file mode 100644 index 0000000..2661752 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/internal/pemutil/pem.go @@ -0,0 +1,123 @@ +package pemutil + +import ( + "crypto" + "crypto/x509" + "encoding/pem" + "errors" + "fmt" +) + +const ( + certType string = "CERTIFICATE" + keyType string = "PRIVATE KEY" +) + +func ParseCertificates(certsBytes []byte) ([]*x509.Certificate, error) { + objects, err := parseBlocks(certsBytes, certType) + if err != nil { + return nil, err + } + + certs := []*x509.Certificate{} + for _, object := range objects { + cert, ok := object.(*x509.Certificate) + if !ok { + return nil, fmt.Errorf("expected *x509.Certificate; got %T", object) + } + certs = append(certs, cert) + } + + return certs, nil +} + +func ParsePrivateKey(keyBytes []byte) (crypto.PrivateKey, error) { + objects, err := parseBlocks(keyBytes, keyType) + if err != nil { + return nil, err + } + if len(objects) == 0 { + return nil, nil + } + + privateKey, ok := objects[0].(crypto.PrivateKey) + if !ok { + return nil, fmt.Errorf("expected crypto.PrivateKey; got %T", objects[0]) + } + return privateKey, nil +} + +func EncodePKCS8PrivateKey(privateKey interface{}) ([]byte, error) { + keyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + return nil, err + } + + return pem.EncodeToMemory(&pem.Block{ + Type: keyType, + Bytes: keyBytes, + }), nil +} + +func EncodeCertificates(certificates []*x509.Certificate) []byte { + pemBytes := []byte{} + for _, cert := range certificates { + pemBytes = append(pemBytes, pem.EncodeToMemory(&pem.Block{ + Type: certType, + Bytes: cert.Raw, + })...) + } + return pemBytes +} + +func parseBlocks(blocksBytes []byte, expectedType string) ([]interface{}, error) { + objects := []interface{}{} + var foundBlocks = false + for { + if len(blocksBytes) == 0 { + if len(objects) == 0 && !foundBlocks { + return nil, errors.New("no PEM blocks found") + } + return objects, nil + } + object, rest, foundBlock, err := parseBlock(blocksBytes, expectedType) + blocksBytes = rest + if foundBlock { + foundBlocks = true + } + switch { + case err != nil: + return nil, err + case object != nil: + objects = append(objects, object) + } + } +} + +func parseBlock(pemBytes []byte, pemType string) (interface{}, []byte, bool, error) { + pemBlock, rest := pem.Decode(pemBytes) + if pemBlock == nil { + return nil, nil, false, nil + } + + if pemBlock.Type != pemType { + return nil, rest, true, nil + } + + var object interface{} + var err error + switch pemType { + case certType: + object, err = x509.ParseCertificate(pemBlock.Bytes) + case keyType: + object, err = x509.ParsePKCS8PrivateKey(pemBlock.Bytes) + default: + err = fmt.Errorf("PEM type not supported: %q", pemType) + } + + if err != nil { + return nil, nil, false, err + } + + return object, rest, true, nil +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/internal/x509util/util.go b/vendor/github.com/spiffe/go-spiffe/v2/internal/x509util/util.go new file mode 100644 index 0000000..c45288d --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/internal/x509util/util.go @@ -0,0 +1,53 @@ +package x509util + +import ( + "crypto/x509" +) + +// NewCertPool returns a new CertPool with the given X.509 certificates +func NewCertPool(certs []*x509.Certificate) *x509.CertPool { + pool := x509.NewCertPool() + for _, cert := range certs { + pool.AddCert(cert) + } + return pool +} + +// CopyX509Authorities copies a slice of X.509 certificates to a new slice. +func CopyX509Authorities(x509Authorities []*x509.Certificate) []*x509.Certificate { + copiedX509Authorities := make([]*x509.Certificate, len(x509Authorities)) + copy(copiedX509Authorities, x509Authorities) + + return copiedX509Authorities +} + +// CertsEqual returns true if the slices of X.509 certificates are equal. +func CertsEqual(a, b []*x509.Certificate) bool { + if len(a) != len(b) { + return false + } + + for i, cert := range a { + if !cert.Equal(b[i]) { + return false + } + } + + return true +} + +func RawCertsFromCerts(certs []*x509.Certificate) [][]byte { + rawCerts := make([][]byte, 0, len(certs)) + for _, cert := range certs { + rawCerts = append(rawCerts, cert.Raw) + } + return rawCerts +} + +func ConcatRawCertsFromCerts(certs []*x509.Certificate) []byte { + var rawCerts []byte + for _, cert := range certs { + rawCerts = append(rawCerts, cert.Raw...) + } + return rawCerts +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/charset_backcompat_allow.go b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/charset_backcompat_allow.go new file mode 100644 index 0000000..9bd225d --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/charset_backcompat_allow.go @@ -0,0 +1,42 @@ +//go:build spiffeid_charset_backcompat +// +build spiffeid_charset_backcompat + +package spiffeid + +func isBackcompatTrustDomainChar(c uint8) bool { + if isSubDelim(c) { + return true + } + switch c { + // unreserved + case '~': + return true + default: + return false + } +} + +func isBackcompatPathChar(c uint8) bool { + if isSubDelim(c) { + return true + } + switch c { + // unreserved + case '~': + return true + // gen-delims + case ':', '[', ']', '@': + return true + default: + return false + } +} + +func isSubDelim(c uint8) bool { + switch c { + case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': + return true + default: + return false + } +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/charset_backcompat_deny.go b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/charset_backcompat_deny.go new file mode 100644 index 0000000..1144747 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/charset_backcompat_deny.go @@ -0,0 +1,12 @@ +//go:build !spiffeid_charset_backcompat +// +build !spiffeid_charset_backcompat + +package spiffeid + +func isBackcompatTrustDomainChar(c uint8) bool { + return false +} + +func isBackcompatPathChar(c uint8) bool { + return false +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/errors.go b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/errors.go new file mode 100644 index 0000000..cc9defe --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/errors.go @@ -0,0 +1,15 @@ +package spiffeid + +import "errors" + +var ( + errBadTrustDomainChar = errors.New("trust domain characters are limited to lowercase letters, numbers, dots, dashes, and underscores") + errBadPathSegmentChar = errors.New("path segment characters are limited to letters, numbers, dots, dashes, and underscores") + errDotSegment = errors.New("path cannot contain dot segments") + errNoLeadingSlash = errors.New("path must have a leading slash") + errEmpty = errors.New("cannot be empty") + errEmptySegment = errors.New("path cannot contain empty segments") + errMissingTrustDomain = errors.New("trust domain is missing") + errTrailingSlash = errors.New("path cannot have a trailing slash") + errWrongScheme = errors.New("scheme is missing or invalid") +) diff --git a/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/id.go b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/id.go new file mode 100644 index 0000000..f4e02ee --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/id.go @@ -0,0 +1,258 @@ +package spiffeid + +import ( + "errors" + "fmt" + "net/url" + "strings" +) + +const ( + schemePrefix = "spiffe://" + schemePrefixLen = len(schemePrefix) +) + +// FromPath returns a new SPIFFE ID in the given trust domain and with the +// given path. The supplied path must be a valid absolute path according to the +// SPIFFE specification. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func FromPath(td TrustDomain, path string) (ID, error) { + if err := ValidatePath(path); err != nil { + return ID{}, err + } + return makeID(td, path) +} + +// FromPathf returns a new SPIFFE ID from the formatted path in the given trust +// domain. The formatted path must be a valid absolute path according to the +// SPIFFE specification. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func FromPathf(td TrustDomain, format string, args ...interface{}) (ID, error) { + path, err := FormatPath(format, args...) + if err != nil { + return ID{}, err + } + return makeID(td, path) +} + +// FromSegments returns a new SPIFFE ID in the given trust domain with joined +// path segments. The path segments must be valid according to the SPIFFE +// specification and must not contain path separators. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func FromSegments(td TrustDomain, segments ...string) (ID, error) { + path, err := JoinPathSegments(segments...) + if err != nil { + return ID{}, err + } + return makeID(td, path) +} + +// FromString parses a SPIFFE ID from a string. +func FromString(id string) (ID, error) { + switch { + case id == "": + return ID{}, errEmpty + case !strings.HasPrefix(id, schemePrefix): + return ID{}, errWrongScheme + } + + pathidx := schemePrefixLen + for ; pathidx < len(id); pathidx++ { + c := id[pathidx] + if c == '/' { + break + } + if !isValidTrustDomainChar(c) { + return ID{}, errBadTrustDomainChar + } + } + + if pathidx == schemePrefixLen { + return ID{}, errMissingTrustDomain + } + + if err := ValidatePath(id[pathidx:]); err != nil { + return ID{}, err + } + + return ID{ + id: id, + pathidx: pathidx, + }, nil +} + +// FromStringf parses a SPIFFE ID from a formatted string. +func FromStringf(format string, args ...interface{}) (ID, error) { + return FromString(fmt.Sprintf(format, args...)) +} + +// FromURI parses a SPIFFE ID from a URI. +func FromURI(uri *url.URL) (ID, error) { + return FromString(uri.String()) +} + +// ID is a SPIFFE ID +type ID struct { + id string + + // pathidx tracks the index to the beginning of the path inside of id. This + // is used when extracting the trust domain or path portions of the id. + pathidx int +} + +// TrustDomain returns the trust domain of the SPIFFE ID. +func (id ID) TrustDomain() TrustDomain { + if id.IsZero() { + return TrustDomain{} + } + return TrustDomain{name: id.id[schemePrefixLen:id.pathidx]} +} + +// MemberOf returns true if the SPIFFE ID is a member of the given trust domain. +func (id ID) MemberOf(td TrustDomain) bool { + return id.TrustDomain() == td +} + +// Path returns the path of the SPIFFE ID inside the trust domain. +func (id ID) Path() string { + return id.id[id.pathidx:] +} + +// String returns the string representation of the SPIFFE ID, e.g., +// "spiffe://example.org/foo/bar". +func (id ID) String() string { + return id.id +} + +// URL returns a URL for SPIFFE ID. +func (id ID) URL() *url.URL { + if id.IsZero() { + return &url.URL{} + } + + return &url.URL{ + Scheme: "spiffe", + Host: id.TrustDomain().String(), + Path: id.Path(), + } +} + +// IsZero returns true if the SPIFFE ID is the zero value. +func (id ID) IsZero() bool { + return id.id == "" +} + +// AppendPath returns an ID with the appended path. It will fail if called on a +// zero value. The path to append must be a valid absolute path according to +// the SPIFFE specification. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func (id ID) AppendPath(path string) (ID, error) { + if id.IsZero() { + return ID{}, errors.New("cannot append path on a zero ID value") + } + if err := ValidatePath(path); err != nil { + return ID{}, err + } + id.id += path + return id, nil +} + +// AppendPathf returns an ID with the appended formatted path. It will fail if +// called on a zero value. The formatted path must be a valid absolute path +// according to the SPIFFE specification. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func (id ID) AppendPathf(format string, args ...interface{}) (ID, error) { + if id.IsZero() { + return ID{}, errors.New("cannot append path on a zero ID value") + } + path, err := FormatPath(format, args...) + if err != nil { + return ID{}, err + } + id.id += path + return id, nil +} + +// AppendSegments returns an ID with the appended joined path segments. It +// will fail if called on a zero value. The path segments must be valid +// according to the SPIFFE specification and must not contain path separators. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func (id ID) AppendSegments(segments ...string) (ID, error) { + if id.IsZero() { + return ID{}, errors.New("cannot append path segments on a zero ID value") + } + path, err := JoinPathSegments(segments...) + if err != nil { + return ID{}, err + } + id.id += path + return id, nil +} + +// Replace path returns an ID with the given path in the same trust domain. It +// will fail if called on a zero value. The given path must be a valid absolute +// path according to the SPIFFE specification. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func (id ID) ReplacePath(path string) (ID, error) { + if id.IsZero() { + return ID{}, errors.New("cannot replace path on a zero ID value") + } + return FromPath(id.TrustDomain(), path) +} + +// ReplacePathf returns an ID with the formatted path in the same trust domain. +// It will fail if called on a zero value. The formatted path must be a valid +// absolute path according to the SPIFFE specification. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func (id ID) ReplacePathf(format string, args ...interface{}) (ID, error) { + if id.IsZero() { + return ID{}, errors.New("cannot replace path on a zero ID value") + } + return FromPathf(id.TrustDomain(), format, args...) +} + +// ReplaceSegments returns an ID with the joined path segments in the same +// trust domain. It will fail if called on a zero value. The path segments must +// be valid according to the SPIFFE specification and must not contain path +// separators. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func (id ID) ReplaceSegments(segments ...string) (ID, error) { + if id.IsZero() { + return ID{}, errors.New("cannot replace path segments on a zero ID value") + } + return FromSegments(id.TrustDomain(), segments...) +} + +// MarshalText returns a text representation of the ID. If the ID is the zero +// value, nil is returned. +func (id ID) MarshalText() ([]byte, error) { + if id.IsZero() { + return nil, nil + } + return []byte(id.String()), nil +} + +// UnmarshalText decodes a text representation of the ID. If the text is empty, +// the ID is set to the zero value. +func (id *ID) UnmarshalText(text []byte) error { + if len(text) == 0 { + *id = ID{} + return nil + } + unmarshaled, err := FromString(string(text)) + if err != nil { + return err + } + *id = unmarshaled + return nil +} + +func makeID(td TrustDomain, path string) (ID, error) { + if td.IsZero() { + return ID{}, errors.New("trust domain is empty") + } + return ID{ + id: schemePrefix + td.name + path, + pathidx: schemePrefixLen + len(td.name), + }, nil +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/match.go b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/match.go new file mode 100644 index 0000000..ae12973 --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/match.go @@ -0,0 +1,47 @@ +package spiffeid + +import "fmt" + +// Matcher is used to match a SPIFFE ID. +type Matcher func(ID) error + +// MatchAny matches any SPIFFE ID. +func MatchAny() Matcher { + return Matcher(func(actual ID) error { + return nil + }) +} + +// MatchID matches a specific SPIFFE ID. +func MatchID(expected ID) Matcher { + return Matcher(func(actual ID) error { + if actual != expected { + return fmt.Errorf("unexpected ID %q", actual) + } + return nil + }) +} + +// MatchOneOf matches any SPIFFE ID in the given list of IDs. +func MatchOneOf(expected ...ID) Matcher { + set := make(map[ID]struct{}) + for _, id := range expected { + set[id] = struct{}{} + } + return Matcher(func(actual ID) error { + if _, ok := set[actual]; !ok { + return fmt.Errorf("unexpected ID %q", actual) + } + return nil + }) +} + +// MatchMemberOf matches any SPIFFE ID in the given trust domain. +func MatchMemberOf(expected TrustDomain) Matcher { + return Matcher(func(actual ID) error { + if !actual.MemberOf(expected) { + return fmt.Errorf("unexpected trust domain %q", actual.TrustDomain()) + } + return nil + }) +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/path.go b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/path.go new file mode 100644 index 0000000..d65dc8f --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/path.go @@ -0,0 +1,107 @@ +package spiffeid + +import ( + "fmt" + "strings" +) + +// FormatPath builds a path by formatting the given formatting string with +// the given args (i.e. fmt.Sprintf). The resulting path must be valid or +// an error is returned. +func FormatPath(format string, args ...interface{}) (string, error) { + path := fmt.Sprintf(format, args...) + if err := ValidatePath(path); err != nil { + return "", err + } + return path, nil +} + +// JoinPathSegments joins one or more path segments into a slash separated +// path. Segments cannot contain slashes. The resulting path must be valid or +// an error is returned. If no segments are provided, an empty string is +// returned. +func JoinPathSegments(segments ...string) (string, error) { + var builder strings.Builder + for _, segment := range segments { + if err := ValidatePathSegment(segment); err != nil { + return "", err + } + builder.WriteByte('/') + builder.WriteString(segment) + } + return builder.String(), nil +} + +// ValidatePath validates that a path string is a conformant path for a SPIFFE +// ID. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func ValidatePath(path string) error { + switch { + case path == "": + return nil + case path[0] != '/': + return errNoLeadingSlash + } + + segmentStart := 0 + segmentEnd := 0 + for ; segmentEnd < len(path); segmentEnd++ { + c := path[segmentEnd] + if c == '/' { + switch path[segmentStart:segmentEnd] { + case "/": + return errEmptySegment + case "/.", "/..": + return errDotSegment + } + segmentStart = segmentEnd + continue + } + if !isValidPathSegmentChar(c) { + return errBadPathSegmentChar + } + } + + switch path[segmentStart:segmentEnd] { + case "/": + return errTrailingSlash + case "/.", "/..": + return errDotSegment + } + return nil +} + +// ValidatePathSegment validates that a string is a conformant segment for +// inclusion in the path for a SPIFFE ID. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#22-path +func ValidatePathSegment(segment string) error { + switch segment { + case "": + return errEmptySegment + case ".", "..": + return errDotSegment + } + for i := 0; i < len(segment); i++ { + if !isValidPathSegmentChar(segment[i]) { + return errBadPathSegmentChar + } + } + return nil +} + +func isValidPathSegmentChar(c uint8) bool { + switch { + case c >= 'a' && c <= 'z': + return true + case c >= 'A' && c <= 'Z': + return true + case c >= '0' && c <= '9': + return true + case c == '-', c == '.', c == '_': + return true + case isBackcompatPathChar(c): + return true + default: + return false + } +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/require.go b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/require.go new file mode 100644 index 0000000..798b54c --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/require.go @@ -0,0 +1,103 @@ +package spiffeid + +import ( + "net/url" +) + +// RequireFromPath is similar to FromPath except that instead of returning an +// error on malformed input, it panics. It should only be used when the input +// is statically verifiable. +func RequireFromPath(td TrustDomain, path string) ID { + id, err := FromPath(td, path) + panicOnErr(err) + return id +} + +// RequireFromPathf is similar to FromPathf except that instead of returning an +// error on malformed input, it panics. It should only be used when the input +// is statically verifiable. +func RequireFromPathf(td TrustDomain, format string, args ...interface{}) ID { + id, err := FromPathf(td, format, args...) + panicOnErr(err) + return id +} + +// RequireFromSegments is similar to FromSegments except that instead of +// returning an error on malformed input, it panics. It should only be used +// when the input is statically verifiable. +func RequireFromSegments(td TrustDomain, segments ...string) ID { + id, err := FromSegments(td, segments...) + panicOnErr(err) + return id +} + +// RequireFromString is similar to FromString except that instead of returning +// an error on malformed input, it panics. It should only be used when the +// input is statically verifiable. +func RequireFromString(s string) ID { + id, err := FromString(s) + panicOnErr(err) + return id +} + +// RequireFromStringf is similar to FromStringf except that instead of +// returning an error on malformed input, it panics. It should only be used +// when the input is statically verifiable. +func RequireFromStringf(format string, args ...interface{}) ID { + id, err := FromStringf(format, args...) + panicOnErr(err) + return id +} + +// RequireFromURI is similar to FromURI except that instead of returning an +// error on malformed input, it panics. It should only be used when the input is +// statically verifiable. +func RequireFromURI(uri *url.URL) ID { + id, err := FromURI(uri) + panicOnErr(err) + return id +} + +// RequireTrustDomainFromString is similar to TrustDomainFromString except that +// instead of returning an error on malformed input, it panics. It should only +// be used when the input is statically verifiable. +func RequireTrustDomainFromString(s string) TrustDomain { + td, err := TrustDomainFromString(s) + panicOnErr(err) + return td +} + +// RequireTrustDomainFromURI is similar to TrustDomainFromURI except that +// instead of returning an error on malformed input, it panics. It should only +// be used when the input is statically verifiable. +func RequireTrustDomainFromURI(uri *url.URL) TrustDomain { + td, err := TrustDomainFromURI(uri) + panicOnErr(err) + return td +} + +// RequireFormatPath builds a path by formatting the given formatting string +// with the given args (i.e. fmt.Sprintf). The resulting path must be valid or +// the function panics. It should only be used when the input is statically +// verifiable. +func RequireFormatPath(format string, args ...interface{}) string { + path, err := FormatPath(format, args...) + panicOnErr(err) + return path +} + +// RequireJoinPathSegments joins one or more path segments into a slash separated +// path. Segments cannot contain slashes. The resulting path must be valid or +// the function panics. It should only be used when the input is statically +// verifiable. +func RequireJoinPathSegments(segments ...string) string { + path, err := JoinPathSegments(segments...) + panicOnErr(err) + return path +} + +func panicOnErr(err error) { + if err != nil { + panic(err) + } +} diff --git a/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/trustdomain.go b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/trustdomain.go new file mode 100644 index 0000000..467ed5e --- /dev/null +++ b/vendor/github.com/spiffe/go-spiffe/v2/spiffeid/trustdomain.go @@ -0,0 +1,127 @@ +package spiffeid + +import ( + "net/url" + "strings" +) + +// TrustDomain represents the trust domain portion of a SPIFFE ID (e.g. +// example.org). +type TrustDomain struct { + name string +} + +// TrustDomainFromString returns a new TrustDomain from a string. The string +// can either be a trust domain name (e.g. example.org), or a valid SPIFFE ID +// URI (e.g. spiffe://example.org), otherwise an error is returned. +// See https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md#21-trust-domain. +func TrustDomainFromString(idOrName string) (TrustDomain, error) { + switch { + case idOrName == "": + return TrustDomain{}, errMissingTrustDomain + case strings.Contains(idOrName, ":/"): + // The ID looks like it has something like a scheme separator, let's + // try to parse as an ID. We use :/ instead of :// since the + // diagnostics are better for a bad input like spiffe:/trustdomain. + id, err := FromString(idOrName) + if err != nil { + return TrustDomain{}, err + } + return id.TrustDomain(), nil + default: + for i := 0; i < len(idOrName); i++ { + if !isValidTrustDomainChar(idOrName[i]) { + return TrustDomain{}, errBadTrustDomainChar + } + } + return TrustDomain{name: idOrName}, nil + } +} + +// TrustDomainFromURI returns a new TrustDomain from a URI. The URI must be a +// valid SPIFFE ID (see FromURI) or an error is returned. The trust domain is +// extracted from the host field. +func TrustDomainFromURI(uri *url.URL) (TrustDomain, error) { + id, err := FromURI(uri) + if err != nil { + return TrustDomain{}, err + } + + return id.TrustDomain(), nil +} + +// Name returns the trust domain name as a string, e.g. example.org. +func (td TrustDomain) Name() string { + return td.name +} + +// String returns the trust domain name as a string, e.g. example.org. +func (td TrustDomain) String() string { + return td.name +} + +// ID returns the SPIFFE ID of the trust domain. +func (td TrustDomain) ID() ID { + if id, err := makeID(td, ""); err == nil { + return id + } + return ID{} +} + +// IDString returns a string representation of the the SPIFFE ID of the trust +// domain, e.g. "spiffe://example.org". +func (td TrustDomain) IDString() string { + return td.ID().String() +} + +// IsZero returns true if the trust domain is the zero value. +func (td TrustDomain) IsZero() bool { + return td.name == "" +} + +// Compare returns an integer comparing the trust domain to another +// lexicographically. The result will be 0 if td==other, -1 if td < other, and +// +1 if td > other. +func (td TrustDomain) Compare(other TrustDomain) int { + return strings.Compare(td.name, other.name) +} + +// MarshalText returns a text representation of the trust domain. If the trust +// domain is the zero value, nil is returned. +func (td TrustDomain) MarshalText() ([]byte, error) { + if td.IsZero() { + return nil, nil + } + return []byte(td.String()), nil +} + +// UnmarshalText decodes a text representation of the trust domain. If the text +// is empty, the trust domain is set to the zero value. +func (td *TrustDomain) UnmarshalText(text []byte) error { + if len(text) == 0 { + *td = TrustDomain{} + return nil + } + + unmarshaled, err := TrustDomainFromString(string(text)) + if err != nil { + return err + } + *td = unmarshaled + return nil +} + +func isValidTrustDomainChar(c uint8) bool { + switch { + case c >= 'a' && c <= 'z': + return true + case c >= '0' && c <= '9': + return true + case c == '-', c == '.', c == '_': + return true + case isBackcompatTrustDomainChar(c): + return true + default: + return false + } +} |
