summaryrefslogtreecommitdiff
path: root/vendor/github.com/spiffe/go-spiffe/v2
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-05-20 14:28:06 -0600
committermo khan <mo@mokhan.ca>2025-05-23 14:49:19 -0600
commit4beee46dc6c7642316e118a4d3aa51e4b407256e (patch)
tree039bdf57b99061844aeb0fe55ad0bc1c864166af /vendor/github.com/spiffe/go-spiffe/v2
parent0ba49bfbde242920d8675a193d7af89420456fc0 (diff)
feat: add external authorization service (authzd) with JWT authentication
- Add new authzd gRPC service implementing Envoy's external authorization API - Integrate JWT authentication filter in Envoy configuration with claim extraction - Update middleware to support both cookie-based and header-based user authentication - Add comprehensive test coverage for authorization service and server - Configure proper service orchestration with authzd, sparkled, and Envoy - Update build system and Docker configuration for multi-service deployment - Add grpcurl tool for gRPC service debugging and testing This enables fine-grained authorization control through Envoy's ext_authz filter while maintaining backward compatibility with existing cookie-based authentication.
Diffstat (limited to 'vendor/github.com/spiffe/go-spiffe/v2')
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/LICENSE201
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/bundle.go200
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/doc.go43
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/set.go105
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/jwtbundle/source.go12
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/bundle.go485
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/doc.go59
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/set.go135
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/spiffebundle/source.go10
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/bundle.go202
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/doc.go42
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/set.go105
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/bundle/x509bundle/source.go12
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/internal/cryptoutil/keys.go34
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/internal/jwtutil/util.go34
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/internal/pemutil/pem.go123
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/internal/x509util/util.go53
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/spiffeid/charset_backcompat_allow.go42
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/spiffeid/charset_backcompat_deny.go12
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/spiffeid/errors.go15
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/spiffeid/id.go258
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/spiffeid/match.go47
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/spiffeid/path.go107
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/spiffeid/require.go103
-rw-r--r--vendor/github.com/spiffe/go-spiffe/v2/spiffeid/trustdomain.go127
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
+ }
+}