-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from cofide/opinionated-ids
Add library for opinionated SPIFFE IDs
- Loading branch information
Showing
8 changed files
with
604 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
// 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" | ||
) | ||
|
||
// 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 { | ||
return err | ||
} | ||
|
||
for _, f := range funcs { | ||
err := f(kv) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
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()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = sid.Matches(funcs...) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
} | ||
|
||
// 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 { | ||
return fmt.Errorf("key %s does not match value %s", key, value) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// 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] != "" { | ||
return fmt.Errorf("key %s is not empty", key) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// 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] == "" { | ||
return fmt.Errorf("key %s is empty", key) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
// 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) | ||
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 | ||
} | ||
} | ||
|
||
// 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)) | ||
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 | ||
} | ||
} | ||
|
||
// 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) | ||
if err == nil { | ||
return errors.New("Did not receive an error in NOT call") | ||
} | ||
|
||
return nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.