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

Permission maps files #613

Merged
merged 13 commits into from
Nov 15, 2022
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Unreleased changes are available as `avenga/couper:edge` container.
* [`trim()` function](https://docs.couper.io/configuration/functions) ([#605](https://github.com/avenga/couper/pull/605))
* OAuth2 client authentication methods (`token_endpoint_auth_method` values) `"client_secret_jwt"` and `"private_key_jwt"` including `jwt_signing_profile` block for [`oauth2`](https://docs.couper.io/configuration/block/oauth2_req_auth), [`beta_oauth2`](https://docs.couper.io/configuration/block/oauth2_ac) and [`oidc`](https://docs.couper.io/configuration/block/oidc) blocks ([#599](https://github.com/avenga/couper/pull/599))
* **mTLS** Support for [`server`](https://docs.couper.io/configuration/block/server_tls) and [`backend`](https://docs.couper.io/configuration/block/backend_tls) blocks ([#615](https://github.com/avenga/couper/pull/615))
* `beta_roles_map_file` and `beta_permissions_map_file` attributes to [`jwt` block](https://docs.couper.io/configuration/block/jwt) ([#613](https://github.com/avenga/couper/pull/613))

* **Changed**
* Replaced the JWT library because the former library was no longer maintained ([#612](https://github.com/avenga/couper/pull/612))
Expand Down
141 changes: 117 additions & 24 deletions accesscontrol/jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"net/http"
"net/http/httptest"
"reflect"
"regexp"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -344,6 +345,9 @@ func Test_JWT_yields_permissions(t *testing.T) {
"user2": {"bar"},
"*": {"default"},
}
permissionsMap := map[string][]string{
"baz": {"blubb"},
}
var noGrantedPermissions []string

tests := []struct {
Expand Down Expand Up @@ -455,13 +459,13 @@ func Test_JWT_yields_permissions(t *testing.T) {
noGrantedPermissions,
},
{
"roles: single string",
"roles: single string, permission mapped",
"",
nil,
"roles",
"admin",
"",
[]string{"foo", "bar", "baz", "default"},
[]string{"foo", "bar", "baz", "default", "blubb"},
},
{
"roles: space-separated list",
Expand Down Expand Up @@ -500,22 +504,22 @@ func Test_JWT_yields_permissions(t *testing.T) {
[]string{"foo", "bar", "default"},
},
{
"roles: list of string, no additional 1",
"roles: list of string, no additional 1, permission mapped",
"",
nil,
"rollen",
[]string{"admin", "user1"},
"",
[]string{"foo", "bar", "baz", "default"},
[]string{"foo", "bar", "baz", "default", "blubb"},
},
{
"roles: list of string, no additional 2",
"roles: list of string, no additional 2, permission mapped",
"",
nil,
"rollen",
[]string{"admin", "user2"},
"",
[]string{"foo", "bar", "baz", "default"},
[]string{"foo", "bar", "baz", "default", "blubb"},
},
{
"roles: warn: boolean",
Expand Down Expand Up @@ -581,13 +585,13 @@ func Test_JWT_yields_permissions(t *testing.T) {
[]string{"foo", "bar", "default"},
},
{
"combi 2",
"combi 2, permission mapped",
"scope",
[]string{"foo", "bar"},
"roles",
"admin",
"",
[]string{"foo", "bar", "baz", "default"},
[]string{"foo", "bar", "baz", "default", "blubb"},
},
}
for _, tt := range tests {
Expand All @@ -612,6 +616,7 @@ func Test_JWT_yields_permissions(t *testing.T) {
Algorithm: algo.String(),
Name: "test_ac",
PermissionsClaim: tt.permissionsClaim,
PermissionsMap: permissionsMap,
RolesClaim: tt.rolesClaim,
RolesMap: rolesMap,
Source: source,
Expand Down Expand Up @@ -679,7 +684,7 @@ func TestJwtConfig(t *testing.T) {
}
}
`,
"signature_algorithm or jwks_url attribute required",
"configuration error: myac: signature_algorithm or jwks_url attribute required",
},
{
"signature_algorithm, missing key/key_file",
Expand All @@ -692,7 +697,84 @@ func TestJwtConfig(t *testing.T) {
}
}
`,
"jwt key: read error: required: configured attribute or file",
"configuration error: myac: jwt key: read error: required: configured attribute or file",
},
{
"signature_algorithm, both key and key_file",
`
server "test" {}
definitions {
jwt "myac" {
signature_algorithm = "HS256"
header = "..."
key = "..."
key_file = "testdata/secret.txt"
}
}
`,
"configuration error: myac: jwt key: read error: configured attribute and file",
},
{
"signature_algorithm, both beta_roles_map and beta_roles_map_file",
`
server "test" {}
definitions {
jwt "myac" {
signature_algorithm = "HS256"
header = "..."
key = "..."
beta_roles_map = {}
beta_roles_map_file = "testdata/map.json"
}
}
`,
"configuration error: myac: jwt roles map: read error: configured attribute and file",
},
{
"signature_algorithm, beta_roles_map_file not found",
`
server "test" {}
definitions {
jwt "myac" {
signature_algorithm = "HS256"
header = "..."
key = "..."
beta_roles_map_file = "file_not_found"
}
}
`,
"configuration error: myac: roles map: read error: open .*/testdata/file_not_found: no such file or directory",
},
{
"signature_algorithm, both beta_permissions_map and beta_permissions_map_file",
`
server "test" {}
definitions {
jwt "myac" {
signature_algorithm = "HS256"
header = "..."
key = "..."
beta_permissions_map = {}
beta_permissions_map_file = "testdata/map.json"
}
}
`,
"configuration error: myac: jwt permissions map: read error: configured attribute and file",
},
{
"signature_algorithm, beta_permissions_map_file not found",
`
server "test" {}
definitions {
jwt "myac" {
signature_algorithm = "HS256"
header = "..."
key = "..."
beta_permissions_map_file = "file_not_found"
}
}
`,
"configuration error: myac: permissions map: read error: open .*/accesscontrol/file_not_found: no such file or directory",
},
{
"ok: signature_algorithm + key (default: header = Authorization)",
Expand Down Expand Up @@ -762,7 +844,7 @@ func TestJwtConfig(t *testing.T) {
}
}
`,
"token source is invalid",
"configuration error: myac: token source is invalid",
},
{
"token_value + cookie",
Expand All @@ -777,7 +859,7 @@ func TestJwtConfig(t *testing.T) {
}
}
`,
"token source is invalid",
"configuration error: myac: token source is invalid",
},
{
"cookie + header",
Expand All @@ -792,7 +874,7 @@ func TestJwtConfig(t *testing.T) {
}
}
`,
"token source is invalid",
"configuration error: myac: token source is invalid",
},
{
"ok: signature_algorithm + key_file",
Expand All @@ -814,13 +896,26 @@ func TestJwtConfig(t *testing.T) {
server "test" {}
definitions {
jwt "myac" {
jwks_url = "file://...",
jwks_url = "file:jwk/testdata/jwks.json",
header = "..."
}
}
`,
"",
},
{
"jwks_url file not found",
`
server "test" {}
definitions {
jwt "myac" {
jwks_url = "file:file_not_found",
header = "..."
}
}
`,
"configuration error: myac: jwks_url: read error: open .*/accesscontrol/file_not_found: no such file or directory",
},
{
"signature_algorithm + jwks_url",
`
Expand All @@ -833,7 +928,7 @@ func TestJwtConfig(t *testing.T) {
}
}
`,
"signature_algorithm cannot be used together with jwks_url",
"configuration error: myac: signature_algorithm cannot be used together with jwks_url",
},
{
"key + jwks_url",
Expand All @@ -847,7 +942,7 @@ func TestJwtConfig(t *testing.T) {
}
}
`,
"key cannot be used together with jwks_url",
"configuration error: myac: key cannot be used together with jwks_url",
},
{
"key_file + jwks_url",
Expand All @@ -861,7 +956,7 @@ func TestJwtConfig(t *testing.T) {
}
}
`,
"key_file cannot be used together with jwks_url",
"configuration error: myac: key_file cannot be used together with jwks_url",
},
{
"backend reference, missing jwks_url",
Expand All @@ -876,11 +971,12 @@ func TestJwtConfig(t *testing.T) {
backend "foo" {}
}
`,
"backend is obsolete without jwks_url attribute",
"configuration error: myac: backend is obsolete without jwks_url attribute",
},
}

log, hook := test.NewLogger()
helper := test.New(t)

for _, tt := range tests {
t.Run(tt.name, func(subT *testing.T) {
Expand All @@ -907,11 +1003,6 @@ func TestJwtConfig(t *testing.T) {
} else {
errMsg = err.Error()
}
if tt.error != "" {
expectedError = "configuration error: myac: " + tt.error
}
} else {
expectedError = tt.error
}

if tt.error == "" && errMsg == "" {
Expand All @@ -928,7 +1019,9 @@ func TestJwtConfig(t *testing.T) {
break
}

if !strings.HasPrefix(errMsg, expectedError) {
re, err := regexp.Compile(expectedError)
helper.Must(err)
if !re.MatchString(errMsg) {
subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot: %q", tt.name, expectedError, errMsg)
}
})
Expand Down
5 changes: 5 additions & 0 deletions accesscontrol/testdata/map.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"a": ["a1", "a2"],
"b": ["b1"],
"c": []
}
14 changes: 8 additions & 6 deletions config/ac_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,19 @@ type JWT struct {
JWKsURL string `hcl:"jwks_url,optional" docs:"URI pointing to a set of [JSON Web Keys (RFC 7517)](https://datatracker.ietf.org/doc/html/rfc7517)"`
JWKsTTL string `hcl:"jwks_ttl,optional" docs:"Time period the JWK set stays valid and may be cached." type:"duration" default:"1h"`
JWKsMaxStale string `hcl:"jwks_max_stale,optional" docs:"Time period the cached JWK set stays valid after its TTL has passed." type:"duration" default:"1h"`
Key string `hcl:"key,optional" docs:"Public key (in PEM format) for {RS*} and {ES*} variants or the secret for {HS*} algorithm."`
KeyFile string `hcl:"key_file,optional" docs:"Optional file reference instead of {key} usage."`
Key string `hcl:"key,optional" docs:"Public key (in PEM format) for {RS*} and {ES*} variants or the secret for {HS*} algorithm. Mutually exclusive with {key_file}."`
KeyFile string `hcl:"key_file,optional" docs:"Reference to file containing verification key. Mutually exclusive with {key}. See {key} for more information."`
Name string `hcl:"name,label"`
Remain hcl.Body `hcl:",remain"`
RolesClaim string `hcl:"beta_roles_claim,optional" docs:"Name of claim specifying the roles of the user represented by the token. The claim value must either be a string containing a space-separated list of role values or a list of string role values."`
RolesMap map[string][]string `hcl:"beta_roles_map,optional" docs:"Mapping of roles to granted permissions. Non-mapped roles can be assigned with {*} to specific permissions."`
RolesMap map[string][]string `hcl:"beta_roles_map,optional" docs:"Mapping of roles to granted permissions. Non-mapped roles can be assigned with {*} to specific permissions. Mutually exclusive with {beta_roles_map_file}."`
RolesMapFile string `hcl:"beta_roles_map_file,optional" docs:"Reference to JSON file containing role mappings. Mutually exclusive with {beta_roles_map}. See {beta_roles_map} for more information."`
PermissionsClaim string `hcl:"beta_permissions_claim,optional" docs:"Name of claim containing the granted permissions. The claim value must either be a string containing a space-separated list of permissions or a list of string permissions."`
PermissionsMap map[string][]string `hcl:"beta_permissions_map,optional" docs:"Mapping of granted permissions to additional granted permissions. Maps values from {beta_permissions_claim} and those created from {beta_roles_map}. The map is called recursively."`
PermissionsMap map[string][]string `hcl:"beta_permissions_map,optional" docs:"Mapping of granted permissions to additional granted permissions. Maps values from {beta_permissions_claim} and those created from {beta_roles_map}. The map is called recursively. Mutually exclusive with {beta_permissions_map_file}."`
PermissionsMapFile string `hcl:"beta_permissions_map_file,optional" docs:"Reference to JSON file containing permission mappings. Mutually exclusive with {beta_permissions_map}. See {beta_permissions_map} for more information."`
SignatureAlgorithm string `hcl:"signature_algorithm,optional" docs:"Valid values: {RS256}, {RS384}, {RS512}, {HS256}, {HS384}, {HS512}, {ES256}, {ES384}, {ES512}"`
SigningKey string `hcl:"signing_key,optional" docs:"Private key (in PEM format) for {RS*} and {ES*} variants."`
SigningKeyFile string `hcl:"signing_key_file,optional" docs:"Optional file reference instead of {signing_key} usage."`
SigningKey string `hcl:"signing_key,optional" docs:"Private key (in PEM format) for {RS*} and {ES*} variants. Mutually exclusive with {signing_key_file}."`
SigningKeyFile string `hcl:"signing_key_file,optional" docs:"Reference to file containing signing key. Mutually exclusive with {signing_key}. See {signing_key} for more information."`
SigningTTL string `hcl:"signing_ttl,optional" docs:"The token's time-to-live (creates the {exp} claim)." type:"duration"`
TokenValue hcl.Expression `hcl:"token_value,optional" docs:"Expression to obtain the token. Cannot be used together with {cookie} or {header}." type:"string"`

Expand Down
4 changes: 2 additions & 2 deletions config/jwt_signing_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import "github.com/hashicorp/hcl/v2"
type JWTSigningProfile struct {
Claims Claims `hcl:"claims,optional" docs:"claims for the JWT payload, claim values are evaluated per request"`
Headers hcl.Expression `hcl:"headers,optional" docs:"additional header fields for the JWT, {alg} and {typ} cannot be set"`
Key string `hcl:"key,optional" docs:"private key (in PEM format) for {RS*} and {ES*} variants or the secret for {HS*} algorithms"`
KeyFile string `hcl:"key_file,optional" docs:"optional file reference instead of {key} usage"`
Key string `hcl:"key,optional" docs:"private key (in PEM format) for {RS*} and {ES*} variants or the secret for {HS*} algorithms. Mutually exclusive with {key_file}."`
KeyFile string `hcl:"key_file,optional" docs:"reference to file containing signing key. Mutually exclusive with {key}. See {key} for more information."`
Name string `hcl:"name,label,optional"`
SignatureAlgorithm string `hcl:"signature_algorithm" docs:"algorithm used for signing: {\"RS256\"}, {\"RS384\"}, {\"RS512\"}, {\"HS256\"}, {\"HS384\"}, {\"HS512\"}, {\"ES256\"}, {\"ES384\"}, {\"ES512\"}"`
TTL string `hcl:"ttl" docs:"The token's time-to-live, creates the {exp} claim"`
Expand Down
Loading