Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to match bound claims using globs #89

Merged
merged 4 commits into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/ryanuber/go-glob"

log "github.com/hashicorp/go-hclog"
"github.com/mitchellh/pointerstructure"
Expand Down Expand Up @@ -89,7 +90,9 @@ func validateAudience(boundAudiences, audClaim []string, strict bool) error {

// validateBoundClaims checks that all of the claim:value requirements in boundClaims are
// met in allClaims.
func validateBoundClaims(logger log.Logger, boundClaims, allClaims map[string]interface{}) error {
func validateBoundClaims(logger log.Logger, boundClaimsType string, boundClaims, allClaims map[string]interface{}) error {
useGlobs := boundClaimsType == boundClaimsTypeGlob

for claim, expValue := range boundClaims {
actValue := getClaim(logger, allClaims, claim)
if actValue == nil {
Expand All @@ -112,10 +115,22 @@ func validateBoundClaims(logger log.Logger, boundClaims, allClaims map[string]in

scan:
for _, v := range expVals {
for _, av := range actVals {
if av == v {
found = true
break scan
if useGlobs {
vs := v.(string)
for _, av := range actVals {
if avs, ok := av.(string); ok {
if glob.Glob(vs, avs) {
found = true
break scan
}
}
}
} else {
for _, av := range actVals {
if av == v {
found = true
break scan
}
}
}
}
Expand Down
164 changes: 141 additions & 23 deletions claims_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,13 +181,15 @@ func TestValidateAudience(t *testing.T) {

func TestValidateBoundClaims(t *testing.T) {
tests := []struct {
name string
boundClaims map[string]interface{}
allClaims map[string]interface{}
errExpected bool
name string
boundClaimsType string
boundClaims map[string]interface{}
allClaims map[string]interface{}
errExpected bool
}{
{
name: "valid",
name: "valid",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "a",
"bar": "b",
Expand All @@ -199,7 +201,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: false,
},
{
name: "valid - non-string claim",
name: "valid - non-string claim",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": []interface{}{42},
},
Expand All @@ -209,7 +212,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: false,
},
{
name: "valid - boolean claim",
name: "valid - boolean claim",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"email_verified": []interface{}{false},
},
Expand All @@ -219,7 +223,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: false,
},
{
name: "valid - match within list",
name: "valid - match within list",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "a",
},
Expand All @@ -229,7 +234,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: false,
},
{
name: "valid - match list against list",
name: "valid - match list against list",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": []interface{}{"a", "b", "c"},
},
Expand All @@ -239,7 +245,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: false,
},
{
name: "invalid - no match within list",
name: "invalid - no match within list",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "c",
},
Expand All @@ -249,7 +256,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: true,
},
{
name: "invalid - no match list against list",
name: "invalid - no match list against list",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": []interface{}{"a", "b", "c"},
},
Expand All @@ -259,7 +267,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: true,
},
{
name: "valid - extra data",
name: "valid - extra data",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "a",
"bar": "b",
Expand All @@ -272,7 +281,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: false,
},
{
name: "mismatched value",
name: "mismatched value",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "a",
"bar": "b",
Expand All @@ -284,7 +294,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: true,
},
{
name: "missing claim",
name: "missing claim",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "a",
"bar": "b",
Expand All @@ -295,7 +306,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: true,
},
{
name: "valid - JSONPointer",
name: "valid - JSONPointer",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "a",
"/bar/baz/1": "y",
Expand All @@ -309,7 +321,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: false,
},
{
name: "invalid - JSONPointer value mismatch",
name: "invalid - JSONPointer value mismatch",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "a",
"/bar/baz/1": "q",
Expand All @@ -323,7 +336,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: true,
},
{
name: "invalid - JSONPointer not found",
name: "invalid - JSONPointer not found",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"foo": "a",
"/bar/XXX/1243": "q",
Expand All @@ -337,7 +351,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: true,
},
{
name: "valid - match alternates",
name: "valid - match alternates",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"email": []interface{}{"a", "b", "c"},
"color": "green",
Expand All @@ -349,7 +364,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: false,
},
{
name: "invalid - no match alternates",
name: "invalid - no match alternates",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"email": []interface{}{"a", "b", "c"},
"color": "green",
Expand All @@ -361,7 +377,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: true,
},
{
name: "invalid bound claim expected value",
name: "invalid bound claim expected value",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"email": 42,
},
Expand All @@ -371,7 +388,8 @@ func TestValidateBoundClaims(t *testing.T) {
errExpected: true,
},
{
name: "invalid bound claim expected boolean value",
name: "invalid bound claim expected boolean value",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"email_verified": true,
},
Expand All @@ -382,7 +400,8 @@ func TestValidateBoundClaims(t *testing.T) {
},

{
name: "invalid received claim expected value",
name: "invalid received claim expected value",
boundClaimsType: "string",
boundClaims: map[string]interface{}{
"email": "d",
},
kalafut marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -391,9 +410,108 @@ func TestValidateBoundClaims(t *testing.T) {
},
errExpected: true,
},

{
name: "matching glob",
boundClaimsType: "glob",
boundClaims: map[string]interface{}{
"email": "4*",
},
allClaims: map[string]interface{}{
"email": "42",
},
errExpected: false,
},
{
name: "invalid string value",
boundClaimsType: "glob",
boundClaims: map[string]interface{}{
"email": "4*",
},
allClaims: map[string]interface{}{
"email": 42,
},
errExpected: true,
},
{
name: "not matching glob",
boundClaimsType: "glob",
boundClaims: map[string]interface{}{
"email": "4*",
},
allClaims: map[string]interface{}{
"email": "d42",
},
errExpected: true,
},
{
name: "not matching glob",
boundClaimsType: "glob",
boundClaims: map[string]interface{}{
"email": "*2",
},
allClaims: map[string]interface{}{
"email": "42x",
},
errExpected: true,
},
{
pcman312 marked this conversation as resolved.
Show resolved Hide resolved
name: "matching glob in list",
boundClaimsType: "glob",
boundClaims: map[string]interface{}{
"email": []interface{}{"4*d", "42*"},
},
allClaims: map[string]interface{}{
"email": "42x",
},
errExpected: false,
},
{
name: "not matching glob in list",
boundClaimsType: "glob",
boundClaims: map[string]interface{}{
"email": []interface{}{"4*d", "42*"},
},
allClaims: map[string]interface{}{
"email": "43x",
},
errExpected: true,
},
{
name: "non matching integer glob",
boundClaimsType: "glob",
boundClaims: map[string]interface{}{
"email": 42,
},
allClaims: map[string]interface{}{
"email": "42x",
},
errExpected: true,
},
{
name: "valid complex glob",
boundClaimsType: "glob",
boundClaims: map[string]interface{}{
"email": `*@*.com`,
},
allClaims: map[string]interface{}{
"email": "[email protected]",
},
errExpected: false,
},
{
name: "non matching complex glob",
boundClaims: map[string]interface{}{
"email": `r*@*.com`,
},
allClaims: map[string]interface{}{
"email": "[email protected]",
},
errExpected: true,
},
}
for _, tt := range tests {
if err := validateBoundClaims(hclog.NewNullLogger(), tt.boundClaims, tt.allClaims); (err != nil) != tt.errExpected {
if err := validateBoundClaims(hclog.NewNullLogger(), tt.boundClaimsType, tt.boundClaims, tt.allClaims); (err != nil) != tt.errExpected {
t.Errorf("validateBoundClaims(%s) error = %v, wantErr %v", tt.name, err, tt.errExpected)
}
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/ryanuber/go-glob v1.0.0
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
gopkg.in/square/go-jose.v2 v2.3.1
)
2 changes: 1 addition & 1 deletion path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func (b *jwtAuthBackend) pathLogin(ctx context.Context, req *logical.Request, d
return nil, errors.New("unhandled case during login")
}

if err := validateBoundClaims(b.Logger(), role.BoundClaims, allClaims); err != nil {
if err := validateBoundClaims(b.Logger(), role.BoundClaimsType, role.BoundClaims, allClaims); err != nil {
return logical.ErrorResponse("error validating claims: %s", err.Error()), nil
}

Expand Down
2 changes: 1 addition & 1 deletion path_oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func (b *jwtAuthBackend) pathCallback(ctx context.Context, req *logical.Request,
}
}

if err := validateBoundClaims(b.Logger(), role.BoundClaims, allClaims); err != nil {
if err := validateBoundClaims(b.Logger(), role.BoundClaimsType, role.BoundClaims, allClaims); err != nil {
return logical.ErrorResponse("error validating claims: %s", err.Error()), nil
}

Expand Down
Loading