From 6406a0a1d9998a4b053afd3d40e11696ecd5705f Mon Sep 17 00:00:00 2001 From: Maartje Eyskens Date: Thu, 12 Dec 2024 11:10:04 +0100 Subject: [PATCH 1/4] Add library for opinionated SPIFFE IDs Signed-off-by: Maartje Eyskens --- go.mod | 10 +++ go.sum | 32 +++++++++ pkg/id/query.go | 129 +++++++++++++++++++++++++++++++++++ pkg/id/query_test.go | 145 ++++++++++++++++++++++++++++++++++++++++ pkg/id/spiffeid.go | 131 ++++++++++++++++++++++++++++++++++++ pkg/id/spiffeid_test.go | 131 ++++++++++++++++++++++++++++++++++++ 6 files changed, 578 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/id/query.go create mode 100644 pkg/id/query_test.go create mode 100644 pkg/id/spiffeid.go create mode 100644 pkg/id/spiffeid_test.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..867cb07 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/cofide/cofide-sdk-go + +go 1.23.3 + +require ( + github.com/gobwas/glob v0.2.3 + github.com/spiffe/go-spiffe/v2 v2.4.0 +) + +require github.com/zeebo/errs v1.3.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5a624b5 --- /dev/null +++ b/go.sum @@ -0,0 +1,32 @@ +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spiffe/go-spiffe/v2 v2.4.0 h1:j/FynG7hi2azrBG5cvjRcnQ4sux/VNj8FAVc99Fl66c= +github.com/spiffe/go-spiffe/v2 v2.4.0/go.mod h1:m5qJ1hGzjxjtrkGHZupoXHo/FDWwCB1MdSyBzfHugx0= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/id/query.go b/pkg/id/query.go new file mode 100644 index 0000000..9df72d9 --- /dev/null +++ b/pkg/id/query.go @@ -0,0 +1,129 @@ +// Copyright 2024 Cofide Limited. +// SPDX-License-Identifier: Apache-2.0 + +package id + +import ( + "crypto/x509" + "errors" + "fmt" + + "github.com/gobwas/glob" + "github.com/spiffe/go-spiffe/v2/spiffeid" + "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" +) + +type MatchFunc func(kv map[string]string) error + +func (s *SPIFFEID) Matches(funcs ...MatchFunc) error { + kv, err := s.ParsePath() + if err != nil { + return err + } + + for _, f := range funcs { + err := f(kv) + if err != nil { + return err + } + } + + return nil +} + +func AuthorizeMatch(funcs ...MatchFunc) tlsconfig.Authorizer { + return func(id spiffeid.ID, verifiedChains [][]*x509.Certificate) error { + sid, err := ParseID(id.String()) + if err != nil { + return err + } + + err = sid.Matches(funcs...) + if err != nil { + return err + } + return nil + } +} + +func Equals(key, value string) MatchFunc { + return func(kv map[string]string) error { + if val, ok := kv[key]; !ok || val != value { + return fmt.Errorf("key %s does not match value %s", key, value) + } + + return nil + } +} + +func IsEmpty(key string) MatchFunc { + return func(kv map[string]string) error { + if kv[key] != "" { + return fmt.Errorf("key %s is not empty", key) + } + + return nil + } +} + +func IsNotEmpty(key string) MatchFunc { + return func(kv map[string]string) error { + if kv[key] == "" { + return fmt.Errorf("key %s is empty", key) + } + + return nil + } +} + +func MatchGlob(key, globStr string) MatchFunc { + return func(kv map[string]string) error { + g, err := glob.Compile(globStr) + if err != nil { + return fmt.Errorf("failed to compile glob %q: %w", globStr, err) + } + if _, ok := kv[key]; !ok { + return fmt.Errorf("key %q not found", key) + } + if !g.Match(kv[key]) { + return fmt.Errorf("key %q with value %q does not match glob %q", key, kv[key], globStr) + } + + return nil + } +} + +func Or(funcs ...MatchFunc) MatchFunc { + return func(kv map[string]string) error { + errs := make([]error, 0, len(funcs)) + for _, f := range funcs { + err := f(kv) + errs = append(errs, err) + } + + hasPassedTest := false + for _, err := range errs { + if err == nil { + hasPassedTest = true + break + } + } + + if !hasPassedTest { + return fmt.Errorf("none of the tests passed") + } + + return nil + } +} + +func Not(f MatchFunc) MatchFunc { + return func(kv map[string]string) error { + err := f(kv) + if err == nil { + return errors.New("Did not receive an error in NOT call") + } + + return nil + } +} diff --git a/pkg/id/query_test.go b/pkg/id/query_test.go new file mode 100644 index 0000000..028e53a --- /dev/null +++ b/pkg/id/query_test.go @@ -0,0 +1,145 @@ +// Copyright 2024 Cofide Limited. +// SPDX-License-Identifier: Apache-2.0 + +package id + +import ( + "testing" +) + +func TestSPIFFEID_Matches(t *testing.T) { + type args struct { + funcs []MatchFunc + } + tests := []struct { + name string + id *SPIFFEID + args args + wantErr bool + }{ + { + name: "Simple KV match", + id: MustParseID("spiffe://example.org/key1/value1/key2/value2"), + args: args{ + funcs: []MatchFunc{ + Equals("key1", "value1"), + Equals("key2", "value2"), + }, + }, + wantErr: false, + }, + { + name: "Simple KV mismatch", + id: MustParseID("spiffe://example.org/key1/value1/key2/value2"), + args: args{ + funcs: []MatchFunc{ + Equals("key1", "value1"), + Equals("key2", "value3"), + }, + }, + wantErr: true, + }, + { + name: "Simple OR", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + Equals("ns", "kube-system"), + Or(Equals("deploy", "kube-dns"), Equals("deploy", "coredns")), + }, + }, + wantErr: false, + }, + { + name: "Simple OR mismatch", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + Equals("ns", "kube-system"), + Or(Equals("deploy", "kube-dns"), Equals("deploy", "kube-proxy")), + }, + }, + wantErr: true, + }, + { + name: "Simple Glob", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + MatchGlob("deploy", "core*"), + }, + }, + wantErr: false, + }, + { + name: "Simple Glob mismatch", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + MatchGlob("deploy", "kube*"), + }, + }, + wantErr: true, + }, + { + name: "Simple isEmpty", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + IsEmpty("cluster"), + }, + }, + wantErr: false, + }, + { + name: "Simple isEmpty mismatch", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + IsEmpty("ns"), + }, + }, + wantErr: true, + }, + { + name: "Simple isNotEmpty", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + IsNotEmpty("ns"), + }, + }, + wantErr: false, + }, + { + name: "Simple isNotEmpty mismatch", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + IsNotEmpty("cluster"), + }, + }, + wantErr: true, + }, + { + name: "Simple Not", + id: MustParseID("spiffe://example.org/ns/kube-system/sa/default/deploy/coredns"), + args: args{ + funcs: []MatchFunc{ + Not(IsEmpty("ns")), + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := tt.id + err := s.Matches(tt.args.funcs...) + if (err != nil) != tt.wantErr { + t.Errorf("SPIFFEID.Matches() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} diff --git a/pkg/id/spiffeid.go b/pkg/id/spiffeid.go new file mode 100644 index 0000000..b6f1fdb --- /dev/null +++ b/pkg/id/spiffeid.go @@ -0,0 +1,131 @@ +// Copyright 2024 Cofide Limited. +// SPDX-License-Identifier: Apache-2.0 + +package id + +import ( + "fmt" + "sort" + "strings" + + "github.com/spiffe/go-spiffe/v2/spiffeid" +) + +// SPIFFEID is an **opinionated** implementation of the upstream spiffeid.ID +// it builds on top of the SPIFFE ID however carries a strong key-value based +// approach to encode the ID with its information. +// URLs are here used as a string representation of trust data as this can be +// used inside X.509 data easily as URI SAN. +// +// the data is encoded as following: +// spiffe:////// +// +// Verifying these IDs should be done by comparing the trust domain and preferably a minimum set of keys. +// If the SPIFFE ID holds (new) keys that are not known to the verifier, the verifier should ignore these keys. +// This allows for a flexible way to encode data in the SPIFFE ID without breaking the verification. +// +// There are a following set of default keys that is recommended be encoded when attested: +// +// Local workloads: +// - uid (user id of caller) +// - gid (user group id of caller) +// - pid (process id) +// - bin (binary name, if available) +// +// Kubernetes workloads: +// - ns (namespace) +// - sa (service account) +// - deploy (deployment name) +type SPIFFEID struct { + // SPIFFE ID + id spiffeid.ID +} + +func NewID(trustDomain string, kv map[string]string) (*SPIFFEID, error) { + // sort the keys to have a deterministic order + keys := make([]string, 0, len(kv)) + for k, v := range kv { + keys = append(keys, k) + if k == "" || v == "" { + return nil, fmt.Errorf("empty key or value not allowed") + } + } + sort.Strings(keys) + + pathKV := make([]string, 0, len(kv)*2) + for _, k := range keys { + pathKV = append(pathKV, k, kv[k]) + } + td, err := spiffeid.TrustDomainFromString(trustDomain) + if err != nil { + return nil, fmt.Errorf("failed to create trust domain: %w", err) + } + + path := "/" + strings.Join(pathKV, "/") + path = strings.TrimSuffix(path, "/") + + id, err := spiffeid.FromPath(td, path) + if err != nil { + return nil, fmt.Errorf("failed to create spiffe id: %w", err) + } + return &SPIFFEID{id: id}, nil +} + +func MustNewID(trustDomain string, kv map[string]string) *SPIFFEID { + id, err := NewID(trustDomain, kv) + if err != nil { + panic(err) + } + return id +} + +func ParseID(id string) (*SPIFFEID, error) { + upstreamID, err := spiffeid.FromString(id) + if err != nil { + return nil, fmt.Errorf("failed to parse spiffe id: %w", err) + } + svid := &SPIFFEID{id: upstreamID} + + if _, err := svid.ParsePath(); err != nil { + return nil, fmt.Errorf("failed to parse path: %w", err) + } + + return svid, nil +} + +func MustParseID(id string) *SPIFFEID { + svid, err := ParseID(id) + if err != nil { + panic(err) + } + return svid +} + +func (s *SPIFFEID) ParsePath() (map[string]string, error) { + path := s.id.Path() + path = strings.Trim(path, "/") + pathParts := strings.Split(path, "/") + + if len(pathParts)%2 != 0 { + return nil, fmt.Errorf("invalid path, needs to be even in parts: %s", path) + } + + kv := make(map[string]string) + for i := 0; i < len(pathParts); i += 2 { + kv[pathParts[i]] = pathParts[i+1] + } + + return kv, nil +} + +func (s *SPIFFEID) TrustDomain() string { + return s.id.TrustDomain().String() +} + +func (s *SPIFFEID) String() string { + return s.id.String() +} + +func (s *SPIFFEID) ToSpiffeID() spiffeid.ID { + return s.id +} diff --git a/pkg/id/spiffeid_test.go b/pkg/id/spiffeid_test.go new file mode 100644 index 0000000..c387a47 --- /dev/null +++ b/pkg/id/spiffeid_test.go @@ -0,0 +1,131 @@ +// Copyright 2024 Cofide Limited. +// SPDX-License-Identifier: Apache-2.0 + +package id + +import ( + "reflect" + "testing" + + "github.com/spiffe/go-spiffe/v2/spiffeid" +) + +func TestParseID(t *testing.T) { + type args struct { + id string + } + tests := []struct { + name string + args args + want *SPIFFEID + wantErr bool + }{ + { + name: "test parse of a spiffe ID", + args: args{ + id: "spiffe://example.com/ns/default/sa/default", + }, + want: &SPIFFEID{ + id: spiffeid.RequireFromPath(spiffeid.RequireTrustDomainFromString("example.com"), "/ns/default/sa/default"), + }, + }, + { + name: "test parse of a spiffe ID with incorrect path KV pairs", + args: args{ + id: "spiffe://example.com/ns/default/sa", + }, + wantErr: true, + }, + { + name: "test parse of a spiffe ID with incorrect path slashes", + args: args{ + id: "spiffe://example.com/ns/default///sa", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseID(tt.args.id) + if (err != nil) != tt.wantErr { + t.Errorf("ParseID() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseID() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestNewID(t *testing.T) { + type args struct { + trustDomain string + kv map[string]string + } + tests := []struct { + name string + args args + want *SPIFFEID + wantErr bool + }{ + { + name: "Create SPIFFE ID", + args: args{ + trustDomain: "example.com", + kv: map[string]string{"ns": "test", "sa": "default"}, + }, + want: &SPIFFEID{ + id: spiffeid.RequireFromPath(spiffeid.RequireTrustDomainFromString("example.com"), "/ns/test/sa/default"), + }, + }, + { + name: "Create SPIFFE ID with empty trust domain", + args: args{ + trustDomain: "", + kv: map[string]string{"ns": "test", "sa": "default"}, + }, + wantErr: true, + }, + { + name: "Create SPIFFE ID with empty key value", + args: args{ + trustDomain: "example.com", + kv: map[string]string{}, + }, + want: &SPIFFEID{ + id: spiffeid.RequireFromPath(spiffeid.RequireTrustDomainFromString("example.com"), ""), + }, + }, + { + name: "Create SPIFFE ID with nil key value", + args: args{ + trustDomain: "example.com", + kv: nil, + }, + want: &SPIFFEID{ + id: spiffeid.RequireFromPath(spiffeid.RequireTrustDomainFromString("example.com"), ""), + }, + }, + { + name: "Create SPIFFE ID with empty key", + args: args{ + trustDomain: "example.com", + kv: map[string]string{"": "test"}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewID(tt.args.trustDomain, tt.args.kv) + if (err != nil) != tt.wantErr { + t.Errorf("NewID() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewID() = %v, want %v", got, tt.want) + } + }) + } +} From 94f3fecad6e9c76656db74546a29a477459d9bd2 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 21 Jan 2025 09:52:37 +0000 Subject: [PATCH 2/4] Sync CI workflow with cofidectl - Don't run pull request workflow on labeled/unlabeled events - CI: Run Go tests with race detector enabled --- .github/workflows/ci.yml | 9 ++++----- Justfile | 6 ++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ccf6689..f14f670 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,6 @@ on: branches: - main pull_request: - types: [opened, synchronize, reopened, labeled, unlabeled] jobs: lint: @@ -17,13 +16,13 @@ jobs: uses: actions/setup-go@v5 with: go-version-file: go.mod - - name: Install golangci-lint + - name: Run golangci-lint uses: golangci/golangci-lint-action@v6 with: args: --timeout=5m build-test: - name: build + name: test runs-on: ubuntu-latest steps: - name: Checkout @@ -37,5 +36,5 @@ jobs: - name: Install dependencies run: | go mod download - - name: Build and run tests - run: just test + - name: Build and run tests with race detector enabled + run: just test-race diff --git a/Justfile b/Justfile index 3c2bd75..afb96ac 100644 --- a/Justfile +++ b/Justfile @@ -1,5 +1,7 @@ -test: - go run gotest.tools/gotestsum@latest --format github-actions ./... +test *args: + go run gotest.tools/gotestsum@latest --format github-actions ./... {{args}} + +test-race: (test "--" "-race") lint *args: golangci-lint run --show-stats {{args}} From de08539cd25ef975c420bc9e8841a21c30ec8f05 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 21 Jan 2025 10:17:53 +0000 Subject: [PATCH 3/4] Drop opinions on which keys to include in SPIFFE IDs --- pkg/id/spiffeid.go | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/pkg/id/spiffeid.go b/pkg/id/spiffeid.go index b6f1fdb..b6a7c02 100644 --- a/pkg/id/spiffeid.go +++ b/pkg/id/spiffeid.go @@ -11,31 +11,21 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffeid" ) -// SPIFFEID is an **opinionated** implementation of the upstream spiffeid.ID -// it builds on top of the SPIFFE ID however carries a strong key-value based -// approach to encode the ID with its information. -// URLs are here used as a string representation of trust data as this can be -// used inside X.509 data easily as URI SAN. +// SPIFFEID is an **opinionated** implementation of the upstream [spiffeid.ID]. +// It builds on top of the SPIFFE ID however carries a strong key-value based +// approach to encode the ID with information. +// URLs are used as a string representation of trust data as this can be used +// inside X.509 data easily as URI SAN. // -// the data is encoded as following: +// The data is encoded as follows: // spiffe:////// // +// For example: +// spiffe://foo.example.org/ns/production/sa/billing +// // Verifying these IDs should be done by comparing the trust domain and preferably a minimum set of keys. // If the SPIFFE ID holds (new) keys that are not known to the verifier, the verifier should ignore these keys. // This allows for a flexible way to encode data in the SPIFFE ID without breaking the verification. -// -// There are a following set of default keys that is recommended be encoded when attested: -// -// Local workloads: -// - uid (user id of caller) -// - gid (user group id of caller) -// - pid (process id) -// - bin (binary name, if available) -// -// Kubernetes workloads: -// - ns (namespace) -// - sa (service account) -// - deploy (deployment name) type SPIFFEID struct { // SPIFFE ID id spiffeid.ID From 099432b11f203cb2b2c11ab03dac0f8f22f6f7a1 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Tue, 21 Jan 2025 10:18:21 +0000 Subject: [PATCH 4/4] Add some more godoc for exported functions and methods --- pkg/id/query.go | 20 ++++++++++++++++++++ pkg/id/spiffeid.go | 8 ++++++++ 2 files changed, 28 insertions(+) diff --git a/pkg/id/query.go b/pkg/id/query.go index 9df72d9..b547cb6 100644 --- a/pkg/id/query.go +++ b/pkg/id/query.go @@ -13,8 +13,14 @@ import ( "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" ) +// MatchFunc is a function that can be called to determine whether the path +// component of a SPIFFEID matches a given constraint. The function should +// return nil if the constraint matches, or an error otherwise. type MatchFunc func(kv map[string]string) error +// Matches applies a set of MatchFunc functions to a SPIFFEID and returns the +// combined match result. If no MatchFunc returns an error, then Match returns +// nil. Otherwise an error is returned. func (s *SPIFFEID) Matches(funcs ...MatchFunc) error { kv, err := s.ParsePath() if err != nil { @@ -31,6 +37,8 @@ func (s *SPIFFEID) Matches(funcs ...MatchFunc) error { return nil } +// AuthorizeMatch returns a [tlsconfig.Authorizer] that authorizes an ID when +// it matches all of the provided MatchFunc. func AuthorizeMatch(funcs ...MatchFunc) tlsconfig.Authorizer { return func(id spiffeid.ID, verifiedChains [][]*x509.Certificate) error { sid, err := ParseID(id.String()) @@ -46,6 +54,8 @@ func AuthorizeMatch(funcs ...MatchFunc) tlsconfig.Authorizer { } } +// Equals returns a MatchFunc that matches any ID that contains the specified +// key/value pair. func Equals(key, value string) MatchFunc { return func(kv map[string]string) error { if val, ok := kv[key]; !ok || val != value { @@ -56,6 +66,8 @@ func Equals(key, value string) MatchFunc { } } +// IsEmptyKey returns a MatchFunc that matches any ID that contains the +// specified key with an empty value. func IsEmpty(key string) MatchFunc { return func(kv map[string]string) error { if kv[key] != "" { @@ -66,6 +78,8 @@ func IsEmpty(key string) MatchFunc { } } +// IsNotEmpty returns a MatchFunc that matches any ID that contains the +// specified key with a non-empty value. func IsNotEmpty(key string) MatchFunc { return func(kv map[string]string) error { if kv[key] == "" { @@ -76,6 +90,8 @@ func IsNotEmpty(key string) MatchFunc { } } +// MatchGlob returns a MatchFunc that matches any ID that contains the +// specified key with a value matching the specified glob pattern. func MatchGlob(key, globStr string) MatchFunc { return func(kv map[string]string) error { g, err := glob.Compile(globStr) @@ -93,6 +109,8 @@ func MatchGlob(key, globStr string) MatchFunc { } } +// Or returns a MatchFunc that combines the specified MatchFunc using a logical +// OR. func Or(funcs ...MatchFunc) MatchFunc { return func(kv map[string]string) error { errs := make([]error, 0, len(funcs)) @@ -117,6 +135,8 @@ func Or(funcs ...MatchFunc) MatchFunc { } } +// Not returns a MatchFunc that logically inverts the result of the specified +// MatchFunc. func Not(f MatchFunc) MatchFunc { return func(kv map[string]string) error { err := f(kv) diff --git a/pkg/id/spiffeid.go b/pkg/id/spiffeid.go index b6a7c02..5ff3330 100644 --- a/pkg/id/spiffeid.go +++ b/pkg/id/spiffeid.go @@ -31,6 +31,7 @@ type SPIFFEID struct { id spiffeid.ID } +// NewID creates a SPIFFEID from a trust domain and key-value map. func NewID(trustDomain string, kv map[string]string) (*SPIFFEID, error) { // sort the keys to have a deterministic order keys := make([]string, 0, len(kv)) @@ -61,6 +62,7 @@ func NewID(trustDomain string, kv map[string]string) (*SPIFFEID, error) { return &SPIFFEID{id: id}, nil } +// MustNewID is the same as NewID, but panics on error. func MustNewID(trustDomain string, kv map[string]string) *SPIFFEID { id, err := NewID(trustDomain, kv) if err != nil { @@ -69,6 +71,7 @@ func MustNewID(trustDomain string, kv map[string]string) *SPIFFEID { return id } +// ParseID parses a SPIFFE ID provided as a string and returns a SPIFFEID. func ParseID(id string) (*SPIFFEID, error) { upstreamID, err := spiffeid.FromString(id) if err != nil { @@ -83,6 +86,7 @@ func ParseID(id string) (*SPIFFEID, error) { return svid, nil } +// MustParseID is the same as ParseID, but panics on error. func MustParseID(id string) *SPIFFEID { svid, err := ParseID(id) if err != nil { @@ -91,6 +95,7 @@ func MustParseID(id string) *SPIFFEID { return svid } +// ParsePath parses the path component of a SPIFFEID and returns it as a map. func (s *SPIFFEID) ParsePath() (map[string]string, error) { path := s.id.Path() path = strings.Trim(path, "/") @@ -108,14 +113,17 @@ func (s *SPIFFEID) ParsePath() (map[string]string, error) { return kv, nil } +// TrustDomain returns the trust domain of a SPIFFEID as a string. func (s *SPIFFEID) TrustDomain() string { return s.id.TrustDomain().String() } +// String returns a string representation of the SPIFFEID. func (s *SPIFFEID) String() string { return s.id.String() } +// ToSpiffeID returns a [spiffeid.ID] representation of the SPIFFEID. func (s *SPIFFEID) ToSpiffeID() spiffeid.ID { return s.id }