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

Missing scope or roles claims #380

Merged
merged 14 commits into from
Nov 25, 2021
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Unreleased changes are available as `avenga/couper:edge` container.
* **Changed**
* [`server` block](./docs/REFERENCE.md#server-block) label is now optional, [`api` block](./docs/REFERENCE.md#api-block) may be labelled ([#358](https://github.com/avenga/couper/pull/358))
* Timings in logs are now numeric values ([#367](https://github.com/avenga/couper/issues/367))
* Missing [scope or roles claims](./docs/REFERENCE.md#jwt-block), or scope or roles claim with unsupported values are now ignored instead of causing an error ([#380](https://github.com/avenga/couper/issues/380))

* **Fixed**
* Handling of [`accept_forwarded_url`](./docs/REFERENCE.md#settings-block) "host" if `H-Forwarded-Host` request header field contains a port ([#360](https://github.com/avenga/couper/pull/360))
Expand Down
156 changes: 89 additions & 67 deletions accesscontrol/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/dgrijalva/jwt-go/v4"
"github.com/hashicorp/hcl/v2"
"github.com/sirupsen/logrus"

acjwt "github.com/avenga/couper/accesscontrol/jwt"
"github.com/avenga/couper/config/request"
Expand Down Expand Up @@ -236,21 +237,17 @@ func (j *JWT) Validate(req *http.Request) error {
acMap[j.name] = tokenClaims
ctx = context.WithValue(ctx, request.AccessControls, acMap)

scopesValues, err := j.getScopeValues(tokenClaims)
if err != nil {
return err
log := req.Context().Value(request.LogEntry).(*logrus.Entry).WithContext(req.Context())
scopesValues := j.getScopeValues(tokenClaims, log)

scopes, ok := ctx.Value(request.Scopes).([]string)
if !ok {
scopes = []string{}
}
alex-schneider marked this conversation as resolved.
Show resolved Hide resolved

if len(scopesValues) > 0 {
scopes, ok := ctx.Value(request.Scopes).([]string)
if !ok {
scopes = []string{}
}
scopes = append(scopes, scopesValues...)

scopes = append(scopes, scopesValues...)

ctx = context.WithValue(ctx, request.Scopes, scopes)
}
ctx = context.WithValue(ctx, request.Scopes, scopes)

*req = *req.WithContext(ctx)

Expand Down Expand Up @@ -322,81 +319,106 @@ func (j *JWT) validateClaims(token *jwt.Token, claims map[string]interface{}) (m
return tokenClaims, nil
}

const errValueMsg = "value of %s claim must either be a string containing a space-separated list of %s or a list of string %s"
func (j *JWT) getScopeValues(tokenClaims map[string]interface{}, log *logrus.Entry) []string {
var scopeValues []string

var errScopeValue = fmt.Errorf(errValueMsg, "scope", "scope values", "scope values")
var errRolesValue = fmt.Errorf(errValueMsg, "roles", "roles", "roles")
scopeValues = j.addScopeValueFromScope(tokenClaims, scopeValues, log)

func (j *JWT) getScopeValues(tokenClaims map[string]interface{}) ([]string, error) {
var scopeValues []string
scopeValues = j.addScopeValueFromRoles(tokenClaims, scopeValues, log)

if j.scopeClaim != "" {
scopesFromClaim, exists := tokenClaims[j.scopeClaim]
if !exists {
return nil, fmt.Errorf("missing expected scope claim %q", j.scopeClaim)
}
return scopeValues
}

// ["foo", "bar"] is stored as []interface{}, not []string, unfortunately
scopesArray, ok := scopesFromClaim.([]interface{})
if ok {
for _, v := range scopesArray {
s, ok := v.(string)
if !ok {
return nil, errScopeValue
}
scopeValues = addScopeValue(scopeValues, s)
}
} else {
scopesString, ok := scopesFromClaim.(string)
const warnInvalidValueMsg = "invalid %s claim value type, ignoring claim, value: %#v"

func (j *JWT) addScopeValueFromScope(tokenClaims map[string]interface{}, scopeValues []string, log *logrus.Entry) []string {
if j.scopeClaim == "" {
return scopeValues
}

scopesFromClaim, exists := tokenClaims[j.scopeClaim]
if !exists {
return scopeValues
}

// ["foo", "bar"] is stored as []interface{}, not []string, unfortunately
scopesArray, ok := scopesFromClaim.([]interface{})
if ok {
var vals []string
for _, v := range scopesArray {
s, ok := v.(string)
if !ok {
return nil, errScopeValue
}
for _, s := range strings.Split(scopesString, " ") {
scopeValues = addScopeValue(scopeValues, s)
log.Warn(fmt.Sprintf(warnInvalidValueMsg, "scope", scopesFromClaim))
return scopeValues
alex-schneider marked this conversation as resolved.
Show resolved Hide resolved
}
vals = append(vals, s)
}
}

if j.rolesClaim != "" {
rolesClaimValue, exists := tokenClaims[j.rolesClaim]
if !exists {
return nil, fmt.Errorf("missing expected roles claim %q", j.rolesClaim)
for _, val := range vals {
scopeValues = addScopeValue(scopeValues, val)
}
} else {
scopesString, ok := scopesFromClaim.(string)
if !ok {
log.Warn(fmt.Sprintf(warnInvalidValueMsg, "scope", scopesFromClaim))
return scopeValues
}
for _, s := range strings.Split(scopesString, " ") {
scopeValues = addScopeValue(scopeValues, s)
}
}
return scopeValues
}

var roleValues []string
// ["foo", "bar"] is stored as []interface{}, not []string, unfortunately
rolesArray, ok := rolesClaimValue.([]interface{})
if ok {
for _, v := range rolesArray {
r, ok := v.(string)
if !ok {
return nil, errRolesValue
}
roleValues = append(roleValues, r)
}
} else {
rolesString, ok := rolesClaimValue.(string)
func (j *JWT) getRoleValues(rolesClaimValue interface{}, log *logrus.Entry) []string {
var roleValues []string
// ["foo", "bar"] is stored as []interface{}, not []string, unfortunately
rolesArray, ok := rolesClaimValue.([]interface{})
if ok {
var vals []string
for _, v := range rolesArray {
r, ok := v.(string)
if !ok {
return nil, errRolesValue
log.Warn(fmt.Sprintf(warnInvalidValueMsg, "roles", rolesClaimValue))
return roleValues
alex-schneider marked this conversation as resolved.
Show resolved Hide resolved
}
roleValues = strings.Split(rolesString, " ")
vals = append(vals, r)
}
for _, r := range roleValues {
if scopes, exist := j.rolesMap[r]; exist {
for _, s := range scopes {
scopeValues = addScopeValue(scopeValues, s)
}
}
return vals
} else {
rolesString, ok := rolesClaimValue.(string)
if !ok {
log.Warn(fmt.Sprintf(warnInvalidValueMsg, "roles", rolesClaimValue))
return roleValues
}
return strings.Split(rolesString, " ")
}
}

func (j *JWT) addScopeValueFromRoles(tokenClaims map[string]interface{}, scopeValues []string, log *logrus.Entry) []string {
if j.rolesClaim == "" || j.rolesMap == nil {
return scopeValues
}

rolesClaimValue, exists := tokenClaims[j.rolesClaim]
if !exists {
return scopeValues
}

if scopes, exist := j.rolesMap["*"]; exist {
roleValues := j.getRoleValues(rolesClaimValue, log)
for _, r := range roleValues {
if scopes, exist := j.rolesMap[r]; exist {
for _, s := range scopes {
scopeValues = addScopeValue(scopeValues, s)
}
}
}

return scopeValues, nil
if scopes, exist := j.rolesMap["*"]; exist {
for _, s := range scopes {
scopeValues = addScopeValue(scopeValues, s)
}
}
return scopeValues
}

func addScopeValue(scopeValues []string, scope string) []string {
Expand Down
Loading