[NET-3092] JWT Verify claims handling (#17452)
* [NET-3092] JWT Verify claims handling
This commit is contained in:
parent
24cb491c89
commit
e273c08fda
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
mesh: Support configuring JWT authentication in Envoy.
|
||||||
|
```
|
|
@ -16,6 +16,18 @@ import (
|
||||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jwtEnvoyFilter = "envoy.filters.http.jwt_authn"
|
||||||
|
jwtMetadataKeyPrefix = "jwt_payload"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is an intermediate JWTProvider form used to associate
|
||||||
|
// unique payload keys to providers
|
||||||
|
type jwtAuthnProvider struct {
|
||||||
|
ComputedName string
|
||||||
|
Provider *structs.IntentionJWTProvider
|
||||||
|
}
|
||||||
|
|
||||||
func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intentions structs.SimplifiedIntentions) (*envoy_http_v3.HttpFilter, error) {
|
func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intentions structs.SimplifiedIntentions) (*envoy_http_v3.HttpFilter, error) {
|
||||||
providers := map[string]*envoy_http_jwt_authn_v3.JwtProvider{}
|
providers := map[string]*envoy_http_jwt_authn_v3.JwtProvider{}
|
||||||
var rules []*envoy_http_jwt_authn_v3.RequirementRule
|
var rules []*envoy_http_jwt_authn_v3.RequirementRule
|
||||||
|
@ -24,29 +36,33 @@ func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intention
|
||||||
if intention.JWT == nil && !hasJWTconfig(intention.Permissions) {
|
if intention.JWT == nil && !hasJWTconfig(intention.Permissions) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, jwtReq := range collectJWTRequirements(intention) {
|
for _, jwtReq := range collectJWTAuthnProviders(intention) {
|
||||||
if _, ok := providers[jwtReq.Name]; ok {
|
if _, ok := providers[jwtReq.ComputedName]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtProvider, ok := pCE[jwtReq.Name]
|
jwtProvider, ok := pCE[jwtReq.Provider.Name]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("provider specified in intention does not exist. Provider name: %s", jwtReq.Name)
|
return nil, fmt.Errorf("provider specified in intention does not exist. Provider name: %s", jwtReq.Provider.Name)
|
||||||
}
|
}
|
||||||
envoyCfg, err := buildJWTProviderConfig(jwtProvider)
|
// If intention permissions use HTTP-match criteria with
|
||||||
|
// VerifyClaims, then generate a clone of the jwt provider with a
|
||||||
|
// unique key for payload_in_metadata. The RBAC filter relies on
|
||||||
|
// the key to check the correct claims for the matched request.
|
||||||
|
envoyCfg, err := buildJWTProviderConfig(jwtProvider, jwtReq.ComputedName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
providers[jwtReq.Name] = envoyCfg
|
providers[jwtReq.ComputedName] = envoyCfg
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, perm := range intention.Permissions {
|
for k, perm := range intention.Permissions {
|
||||||
if perm.JWT == nil {
|
if perm.JWT == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, prov := range perm.JWT.Providers {
|
for _, prov := range perm.JWT.Providers {
|
||||||
rule := buildRouteRule(prov, perm, "/")
|
rule := buildRouteRule(prov, perm, "/", k)
|
||||||
rules = append(rules, rule)
|
rules = append(rules, rule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,8 +70,7 @@ func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intention
|
||||||
if intention.JWT != nil {
|
if intention.JWT != nil {
|
||||||
for _, provider := range intention.JWT.Providers {
|
for _, provider := range intention.JWT.Providers {
|
||||||
// The top-level provider applies to all requests.
|
// The top-level provider applies to all requests.
|
||||||
// TODO(roncodingenthusiast): Handle provider.VerifyClaims
|
rule := buildRouteRule(provider, nil, "/", 0)
|
||||||
rule := buildRouteRule(provider, nil, "/")
|
|
||||||
rules = append(rules, rule)
|
rules = append(rules, rule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,37 +85,65 @@ func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intention
|
||||||
Providers: providers,
|
Providers: providers,
|
||||||
Rules: rules,
|
Rules: rules,
|
||||||
}
|
}
|
||||||
return makeEnvoyHTTPFilter("envoy.filters.http.jwt_authn", cfg)
|
return makeEnvoyHTTPFilter(jwtEnvoyFilter, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectJWTRequirements(i *structs.Intention) []*structs.IntentionJWTProvider {
|
func collectJWTAuthnProviders(i *structs.Intention) []*jwtAuthnProvider {
|
||||||
var jReqs []*structs.IntentionJWTProvider
|
var reqs []*jwtAuthnProvider
|
||||||
|
|
||||||
if i.JWT != nil {
|
if i.JWT != nil {
|
||||||
jReqs = append(jReqs, i.JWT.Providers...)
|
for _, prov := range i.JWT.Providers {
|
||||||
|
reqs = append(reqs, &jwtAuthnProvider{Provider: prov, ComputedName: makeComputedProviderName(prov.Name, nil, 0)})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jReqs = append(jReqs, getPermissionsProviders(i.Permissions)...)
|
reqs = append(reqs, getPermissionsProviders(i.Permissions)...)
|
||||||
|
|
||||||
return jReqs
|
return reqs
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPermissionsProviders(p []*structs.IntentionPermission) []*structs.IntentionJWTProvider {
|
func getPermissionsProviders(p []*structs.IntentionPermission) []*jwtAuthnProvider {
|
||||||
intentionProviders := []*structs.IntentionJWTProvider{}
|
var reqs []*jwtAuthnProvider
|
||||||
for _, perm := range p {
|
for k, perm := range p {
|
||||||
if perm.JWT == nil {
|
if perm.JWT == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
intentionProviders = append(intentionProviders, perm.JWT.Providers...)
|
for _, prov := range perm.JWT.Providers {
|
||||||
|
reqs = append(reqs, &jwtAuthnProvider{Provider: prov, ComputedName: makeComputedProviderName(prov.Name, perm, k)})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return intentionProviders
|
return reqs
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildJWTProviderConfig(p *structs.JWTProviderConfigEntry) (*envoy_http_jwt_authn_v3.JwtProvider, error) {
|
// makeComputedProviderName is used to create names for unique provider per permission
|
||||||
|
// This is to stop jwt claims cross validation across permissions/providers.
|
||||||
|
//
|
||||||
|
// eg. If Permission x is the 3rd permission and has a provider of original name okta
|
||||||
|
// this function will return okta_3 as the computed provider name
|
||||||
|
func makeComputedProviderName(name string, perm *structs.IntentionPermission, idx int) string {
|
||||||
|
if perm == nil {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s_%d", name, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildPayloadInMetadataKey is used to create a unique payload key per provider/permissions.
|
||||||
|
// This is to ensure claims are validated/forwarded specifically under the right permission/path
|
||||||
|
// and ensure we don't accidentally validate claims from different permissions/providers.
|
||||||
|
//
|
||||||
|
// eg. With a provider named okta, the second permission in permission list will have a provider of:
|
||||||
|
// okta_2 and a payload key of: jwt_payload_okta_2. Whereas an okta provider with no specific permission
|
||||||
|
// will have a payload key of: jwt_payload_okta
|
||||||
|
func buildPayloadInMetadataKey(providerName string, perm *structs.IntentionPermission, idx int) string {
|
||||||
|
return fmt.Sprintf("%s_%s", jwtMetadataKeyPrefix, makeComputedProviderName(providerName, perm, idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildJWTProviderConfig(p *structs.JWTProviderConfigEntry, metadataKeySuffix string) (*envoy_http_jwt_authn_v3.JwtProvider, error) {
|
||||||
envoyCfg := envoy_http_jwt_authn_v3.JwtProvider{
|
envoyCfg := envoy_http_jwt_authn_v3.JwtProvider{
|
||||||
Issuer: p.Issuer,
|
Issuer: p.Issuer,
|
||||||
Audiences: p.Audiences,
|
Audiences: p.Audiences,
|
||||||
|
PayloadInMetadata: buildPayloadInMetadataKey(metadataKeySuffix, nil, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Forwarding != nil {
|
if p.Forwarding != nil {
|
||||||
|
@ -216,7 +259,7 @@ func buildJWTRetryPolicy(r *structs.JWKSRetryPolicy) *envoy_core_v3.RetryPolicy
|
||||||
return &pol
|
return &pol
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildRouteRule(provider *structs.IntentionJWTProvider, perm *structs.IntentionPermission, defaultPrefix string) *envoy_http_jwt_authn_v3.RequirementRule {
|
func buildRouteRule(provider *structs.IntentionJWTProvider, perm *structs.IntentionPermission, defaultPrefix string, permIdx int) *envoy_http_jwt_authn_v3.RequirementRule {
|
||||||
rule := &envoy_http_jwt_authn_v3.RequirementRule{
|
rule := &envoy_http_jwt_authn_v3.RequirementRule{
|
||||||
Match: &envoy_route_v3.RouteMatch{
|
Match: &envoy_route_v3.RouteMatch{
|
||||||
PathSpecifier: &envoy_route_v3.RouteMatch_Prefix{Prefix: defaultPrefix},
|
PathSpecifier: &envoy_route_v3.RouteMatch_Prefix{Prefix: defaultPrefix},
|
||||||
|
@ -224,7 +267,7 @@ func buildRouteRule(provider *structs.IntentionJWTProvider, perm *structs.Intent
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
||||||
ProviderName: provider.Name,
|
ProviderName: makeComputedProviderName(provider.Name, perm, permIdx),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -65,21 +65,21 @@ var (
|
||||||
pWithOktaProvider = &structs.IntentionPermission{
|
pWithOktaProvider = &structs.IntentionPermission{
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
HTTP: &structs.IntentionHTTPPermission{
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
PathPrefix: "/some-special-path",
|
PathPrefix: "some-special-path",
|
||||||
},
|
},
|
||||||
JWT: oktaIntention,
|
JWT: oktaIntention,
|
||||||
}
|
}
|
||||||
pWithMultiProviders = &structs.IntentionPermission{
|
pWithMultiProviders = &structs.IntentionPermission{
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
HTTP: &structs.IntentionHTTPPermission{
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
PathPrefix: "/some-special-path",
|
PathPrefix: "some-special-path",
|
||||||
},
|
},
|
||||||
JWT: multiProviderIntentions,
|
JWT: multiProviderIntentions,
|
||||||
}
|
}
|
||||||
pWithNoJWT = &structs.IntentionPermission{
|
pWithNoJWT = &structs.IntentionPermission{
|
||||||
Action: structs.IntentionActionAllow,
|
Action: structs.IntentionActionAllow,
|
||||||
HTTP: &structs.IntentionHTTPPermission{
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
PathPrefix: "/some-special-path",
|
PathPrefix: "some-special-path",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fullRetryPolicy = &structs.JWKSRetryPolicy{
|
fullRetryPolicy = &structs.JWKSRetryPolicy{
|
||||||
|
@ -206,51 +206,123 @@ func TestMakeJWTAUTHFilters(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCollectJWTRequirements(t *testing.T) {
|
func TestMakeComputedProviderName(t *testing.T) {
|
||||||
var (
|
|
||||||
emptyReq = []*structs.IntentionJWTProvider{}
|
|
||||||
oneReq = []*structs.IntentionJWTProvider{&oktaProvider}
|
|
||||||
multiReq = append(oneReq, &auth0Provider)
|
|
||||||
)
|
|
||||||
|
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
intention *structs.Intention
|
name string
|
||||||
expected []*structs.IntentionJWTProvider
|
perm *structs.IntentionPermission
|
||||||
|
idx int
|
||||||
|
expected string
|
||||||
}{
|
}{
|
||||||
"empty-top-level-jwt-and-empty-permissions": {
|
"no-permissions": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web"}),
|
name: "okta",
|
||||||
expected: emptyReq,
|
idx: 0,
|
||||||
|
expected: "okta",
|
||||||
},
|
},
|
||||||
"top-level-jwt-and-empty-permissions": {
|
"exact-path-permission": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: oktaIntention}),
|
name: "auth0",
|
||||||
expected: oneReq,
|
perm: &structs.IntentionPermission{
|
||||||
},
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
"multi-top-level-jwt-and-empty-permissions": {
|
PathExact: "admin",
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: multiProviderIntentions}),
|
},
|
||||||
expected: multiReq,
|
},
|
||||||
},
|
idx: 5,
|
||||||
"top-level-jwt-and-one-jwt-permission": {
|
expected: "auth0_5",
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: auth0Intention, perms: pWithOktaProvider}),
|
|
||||||
expected: multiReq,
|
|
||||||
},
|
|
||||||
"top-level-jwt-and-multi-jwt-permissions": {
|
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: fakeIntention, perms: pWithMultiProviders}),
|
|
||||||
expected: append(multiReq, &fakeProvider),
|
|
||||||
},
|
|
||||||
"empty-top-level-jwt-and-one-jwt-permission": {
|
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithOktaProvider}),
|
|
||||||
expected: oneReq,
|
|
||||||
},
|
|
||||||
"empty-top-level-jwt-and-multi-jwt-permission": {
|
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithMultiProviders}),
|
|
||||||
expected: multiReq,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, tt := range tests {
|
for name, tt := range tests {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
reqs := collectJWTRequirements(tt.intention)
|
reqs := makeComputedProviderName(tt.name, tt.perm, tt.idx)
|
||||||
|
require.Equal(t, reqs, tt.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPayloadInMetadataKey(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
name string
|
||||||
|
perm *structs.IntentionPermission
|
||||||
|
permIdx int
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
"no-permissions": {
|
||||||
|
name: "okta",
|
||||||
|
expected: "jwt_payload_okta",
|
||||||
|
},
|
||||||
|
"path-prefix-permission": {
|
||||||
|
name: "auth0",
|
||||||
|
perm: &structs.IntentionPermission{
|
||||||
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
|
PathPrefix: "admin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
permIdx: 4,
|
||||||
|
expected: "jwt_payload_auth0_4",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
reqs := buildPayloadInMetadataKey(tt.name, tt.perm, tt.permIdx)
|
||||||
|
require.Equal(t, reqs, tt.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectJWTAuthnProviders(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
intention *structs.Intention
|
||||||
|
expected []*jwtAuthnProvider
|
||||||
|
}{
|
||||||
|
"empty-top-level-jwt-and-empty-permissions": {
|
||||||
|
intention: makeTestIntention(t, ixnOpts{src: "web"}),
|
||||||
|
expected: []*jwtAuthnProvider{},
|
||||||
|
},
|
||||||
|
"top-level-jwt-and-empty-permissions": {
|
||||||
|
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: oktaIntention}),
|
||||||
|
expected: []*jwtAuthnProvider{{Provider: &oktaProvider, ComputedName: oktaProvider.Name}},
|
||||||
|
},
|
||||||
|
"multi-top-level-jwt-and-empty-permissions": {
|
||||||
|
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: multiProviderIntentions}),
|
||||||
|
expected: []*jwtAuthnProvider{
|
||||||
|
{Provider: &oktaProvider, ComputedName: oktaProvider.Name},
|
||||||
|
{Provider: &auth0Provider, ComputedName: auth0Provider.Name},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"top-level-jwt-and-one-jwt-permission": {
|
||||||
|
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: auth0Intention, perms: pWithOktaProvider}),
|
||||||
|
expected: []*jwtAuthnProvider{
|
||||||
|
{Provider: &auth0Provider, ComputedName: auth0Provider.Name},
|
||||||
|
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"top-level-jwt-and-multi-jwt-permissions": {
|
||||||
|
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: fakeIntention, perms: pWithMultiProviders}),
|
||||||
|
expected: []*jwtAuthnProvider{
|
||||||
|
{Provider: &fakeProvider, ComputedName: fakeProvider.Name},
|
||||||
|
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
||||||
|
{Provider: &auth0Provider, ComputedName: "auth0_0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"empty-top-level-jwt-and-one-jwt-permission": {
|
||||||
|
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithOktaProvider}),
|
||||||
|
expected: []*jwtAuthnProvider{{Provider: &oktaProvider, ComputedName: "okta_0"}},
|
||||||
|
},
|
||||||
|
"empty-top-level-jwt-and-multi-jwt-permission": {
|
||||||
|
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithMultiProviders}),
|
||||||
|
expected: []*jwtAuthnProvider{
|
||||||
|
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
||||||
|
{Provider: &auth0Provider, ComputedName: "auth0_0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
reqs := collectJWTAuthnProviders(tt.intention)
|
||||||
require.ElementsMatch(t, reqs, tt.expected)
|
require.ElementsMatch(t, reqs, tt.expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -259,27 +331,32 @@ func TestCollectJWTRequirements(t *testing.T) {
|
||||||
func TestGetPermissionsProviders(t *testing.T) {
|
func TestGetPermissionsProviders(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
perms []*structs.IntentionPermission
|
perms []*structs.IntentionPermission
|
||||||
expected []*structs.IntentionJWTProvider
|
expected []*jwtAuthnProvider
|
||||||
}{
|
}{
|
||||||
"empty-permissions": {
|
"empty-permissions": {
|
||||||
perms: []*structs.IntentionPermission{},
|
perms: []*structs.IntentionPermission{},
|
||||||
expected: []*structs.IntentionJWTProvider{},
|
expected: []*jwtAuthnProvider{},
|
||||||
},
|
},
|
||||||
"nil-permissions": {
|
"nil-permissions": {
|
||||||
perms: nil,
|
perms: nil,
|
||||||
expected: []*structs.IntentionJWTProvider{},
|
expected: []*jwtAuthnProvider{},
|
||||||
},
|
},
|
||||||
"permissions-with-no-jwt": {
|
"permissions-with-no-jwt": {
|
||||||
perms: []*structs.IntentionPermission{pWithNoJWT},
|
perms: []*structs.IntentionPermission{pWithNoJWT},
|
||||||
expected: []*structs.IntentionJWTProvider{},
|
expected: []*jwtAuthnProvider{},
|
||||||
},
|
},
|
||||||
"permissions-with-one-jwt": {
|
"permissions-with-one-jwt": {
|
||||||
perms: []*structs.IntentionPermission{pWithOktaProvider, pWithNoJWT},
|
perms: []*structs.IntentionPermission{pWithOktaProvider, pWithNoJWT},
|
||||||
expected: []*structs.IntentionJWTProvider{&oktaProvider},
|
expected: []*jwtAuthnProvider{
|
||||||
|
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"permissions-with-multiple-jwt": {
|
"permissions-with-multiple-jwt": {
|
||||||
perms: []*structs.IntentionPermission{pWithMultiProviders, pWithNoJWT},
|
perms: []*structs.IntentionPermission{pWithMultiProviders, pWithNoJWT},
|
||||||
expected: []*structs.IntentionJWTProvider{&oktaProvider, &auth0Provider},
|
expected: []*jwtAuthnProvider{
|
||||||
|
{Provider: &auth0Provider, ComputedName: "auth0_0"},
|
||||||
|
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,6 +415,7 @@ func TestBuildJWTProviderConfig(t *testing.T) {
|
||||||
Issuer: fullCE.Issuer,
|
Issuer: fullCE.Issuer,
|
||||||
Audiences: fullCE.Audiences,
|
Audiences: fullCE.Audiences,
|
||||||
ForwardPayloadHeader: "user-token",
|
ForwardPayloadHeader: "user-token",
|
||||||
|
PayloadInMetadata: buildPayloadInMetadataKey(ceRemoteJWKS.Name, nil, 0),
|
||||||
PadForwardPayloadHeader: false,
|
PadForwardPayloadHeader: false,
|
||||||
Forward: true,
|
Forward: true,
|
||||||
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_LocalJwks{
|
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_LocalJwks{
|
||||||
|
@ -353,8 +431,9 @@ func TestBuildJWTProviderConfig(t *testing.T) {
|
||||||
"entry-with-remote-jwks": {
|
"entry-with-remote-jwks": {
|
||||||
ce: &ceRemoteJWKS,
|
ce: &ceRemoteJWKS,
|
||||||
expected: &envoy_http_jwt_authn_v3.JwtProvider{
|
expected: &envoy_http_jwt_authn_v3.JwtProvider{
|
||||||
Issuer: fullCE.Issuer,
|
Issuer: fullCE.Issuer,
|
||||||
Audiences: fullCE.Audiences,
|
Audiences: fullCE.Audiences,
|
||||||
|
PayloadInMetadata: buildPayloadInMetadataKey(ceRemoteJWKS.Name, nil, 0),
|
||||||
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks{
|
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks{
|
||||||
RemoteJwks: &envoy_http_jwt_authn_v3.RemoteJwks{
|
RemoteJwks: &envoy_http_jwt_authn_v3.RemoteJwks{
|
||||||
HttpUri: &envoy_core_v3.HttpUri{
|
HttpUri: &envoy_core_v3.HttpUri{
|
||||||
|
@ -374,7 +453,7 @@ func TestBuildJWTProviderConfig(t *testing.T) {
|
||||||
for name, tt := range tests {
|
for name, tt := range tests {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
res, err := buildJWTProviderConfig(tt.ce)
|
res, err := buildJWTProviderConfig(tt.ce, tt.ce.GetName())
|
||||||
|
|
||||||
if tt.expectedError != "" {
|
if tt.expectedError != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
@ -585,7 +664,7 @@ func TestBuildRouteRule(t *testing.T) {
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
||||||
ProviderName: oktaProvider.Name,
|
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithMultiProviders, 0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -602,7 +681,7 @@ func TestBuildRouteRule(t *testing.T) {
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
||||||
ProviderName: oktaProvider.Name,
|
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithExactPath, 0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -619,7 +698,7 @@ func TestBuildRouteRule(t *testing.T) {
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
||||||
ProviderName: oktaProvider.Name,
|
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithRegex, 0),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -630,7 +709,7 @@ func TestBuildRouteRule(t *testing.T) {
|
||||||
for name, tt := range tests {
|
for name, tt := range tests {
|
||||||
tt := tt
|
tt := tt
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
res := buildRouteRule(tt.provider, tt.perm, tt.route)
|
res := buildRouteRule(tt.provider, tt.perm, tt.route, 0)
|
||||||
require.Equal(t, res, tt.expected)
|
require.Equal(t, res, tt.expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,16 +232,40 @@ func intentionToIntermediateRBACForm(
|
||||||
rixn.Source.TrustDomain = bundle.TrustDomain
|
rixn.Source.TrustDomain = bundle.TrustDomain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isHTTP && ixn.JWT != nil {
|
||||||
|
var c []*JWTInfo
|
||||||
|
for _, prov := range ixn.JWT.Providers {
|
||||||
|
if len(prov.VerifyClaims) > 0 {
|
||||||
|
c = append(c, makeJWTInfos(prov, nil, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(c) > 0 {
|
||||||
|
rixn.jwtInfos = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(ixn.Permissions) > 0 {
|
if len(ixn.Permissions) > 0 {
|
||||||
if isHTTP {
|
if isHTTP {
|
||||||
rixn.Action = intentionActionLayer7
|
rixn.Action = intentionActionLayer7
|
||||||
rixn.Permissions = make([]*rbacPermission, 0, len(ixn.Permissions))
|
rixn.Permissions = make([]*rbacPermission, 0, len(ixn.Permissions))
|
||||||
for _, perm := range ixn.Permissions {
|
for k, perm := range ixn.Permissions {
|
||||||
rixn.Permissions = append(rixn.Permissions, &rbacPermission{
|
rbacPerm := rbacPermission{
|
||||||
Definition: perm,
|
Definition: perm,
|
||||||
Action: intentionActionFromString(perm.Action),
|
Action: intentionActionFromString(perm.Action),
|
||||||
Perm: convertPermission(perm),
|
Perm: convertPermission(perm),
|
||||||
})
|
}
|
||||||
|
if perm.JWT != nil {
|
||||||
|
var c []*JWTInfo
|
||||||
|
for _, prov := range perm.JWT.Providers {
|
||||||
|
if len(prov.VerifyClaims) > 0 {
|
||||||
|
c = append(c, makeJWTInfos(prov, perm, k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(c) > 0 {
|
||||||
|
rbacPerm.jwtInfos = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rixn.Permissions = append(rixn.Permissions, &rbacPerm)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// In case L7 intentions slip through to here, treat them as deny intentions.
|
// In case L7 intentions slip through to here, treat them as deny intentions.
|
||||||
|
@ -254,8 +278,17 @@ func intentionToIntermediateRBACForm(
|
||||||
return rixn
|
return rixn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeJWTInfos(p *structs.IntentionJWTProvider, perm *structs.IntentionPermission, permKey int) *JWTInfo {
|
||||||
|
return &JWTInfo{Claims: p.VerifyClaims, MetadataPayloadKey: buildPayloadInMetadataKey(p.Name, perm, permKey)}
|
||||||
|
}
|
||||||
|
|
||||||
type intentionAction int
|
type intentionAction int
|
||||||
|
|
||||||
|
type JWTInfo struct {
|
||||||
|
Claims []*structs.IntentionJWTClaimVerification
|
||||||
|
MetadataPayloadKey string
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
intentionActionDeny intentionAction = iota
|
intentionActionDeny intentionAction = iota
|
||||||
intentionActionAllow
|
intentionActionAllow
|
||||||
|
@ -294,6 +327,11 @@ type rbacIntention struct {
|
||||||
Permissions []*rbacPermission
|
Permissions []*rbacPermission
|
||||||
Precedence int
|
Precedence int
|
||||||
|
|
||||||
|
// JWTInfo is used to track intentions' JWT information
|
||||||
|
// This information is used to update HTTP filters for
|
||||||
|
// JWT Payload & claims validation
|
||||||
|
jwtInfos []*JWTInfo
|
||||||
|
|
||||||
// Skip is field used to indicate that this intention can be deleted in the
|
// Skip is field used to indicate that this intention can be deleted in the
|
||||||
// final pass. Items marked as true should generally not escape the method
|
// final pass. Items marked as true should generally not escape the method
|
||||||
// that marked them.
|
// that marked them.
|
||||||
|
@ -366,6 +404,11 @@ type rbacPermission struct {
|
||||||
Perm *envoy_rbac_v3.Permission
|
Perm *envoy_rbac_v3.Permission
|
||||||
NotPerms []*envoy_rbac_v3.Permission
|
NotPerms []*envoy_rbac_v3.Permission
|
||||||
|
|
||||||
|
// JWTInfo is used to track intentions' JWT information
|
||||||
|
// This information is used to update HTTP filters for
|
||||||
|
// JWT Payload & claims validation
|
||||||
|
jwtInfos []*JWTInfo
|
||||||
|
|
||||||
// Skip is field used to indicate that this permission can be deleted in
|
// Skip is field used to indicate that this permission can be deleted in
|
||||||
// the final pass. Items marked as true should generally not escape the
|
// the final pass. Items marked as true should generally not escape the
|
||||||
// method that marked them.
|
// method that marked them.
|
||||||
|
@ -531,6 +574,10 @@ func makeRBACRules(
|
||||||
|
|
||||||
var principalsL4 []*envoy_rbac_v3.Principal
|
var principalsL4 []*envoy_rbac_v3.Principal
|
||||||
for i, rbacIxn := range rbacIxns {
|
for i, rbacIxn := range rbacIxns {
|
||||||
|
var infos []*JWTInfo
|
||||||
|
if isHTTP {
|
||||||
|
infos = collectJWTInfos(rbacIxn)
|
||||||
|
}
|
||||||
if rbacIxn.Action == intentionActionLayer7 {
|
if rbacIxn.Action == intentionActionLayer7 {
|
||||||
if len(rbacIxn.Permissions) == 0 {
|
if len(rbacIxn.Permissions) == 0 {
|
||||||
panic("invalid state: L7 intention has no permissions")
|
panic("invalid state: L7 intention has no permissions")
|
||||||
|
@ -539,9 +586,14 @@ func makeRBACRules(
|
||||||
panic("invalid state: L7 permissions present for TCP service")
|
panic("invalid state: L7 permissions present for TCP service")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rbacPrincipals := optimizePrincipals([]*envoy_rbac_v3.Principal{rbacIxn.ComputedPrincipal})
|
||||||
|
if len(infos) > 0 {
|
||||||
|
claimsPrincipal := jwtInfosToPrincipals(infos)
|
||||||
|
rbacPrincipals = combineBasePrincipalWithJWTPrincipals(rbacPrincipals, claimsPrincipal)
|
||||||
|
}
|
||||||
// For L7: we should generate one Policy per Principal and list all of the Permissions
|
// For L7: we should generate one Policy per Principal and list all of the Permissions
|
||||||
policy := &envoy_rbac_v3.Policy{
|
policy := &envoy_rbac_v3.Policy{
|
||||||
Principals: optimizePrincipals([]*envoy_rbac_v3.Principal{rbacIxn.ComputedPrincipal}),
|
Principals: rbacPrincipals,
|
||||||
Permissions: make([]*envoy_rbac_v3.Permission, 0, len(rbacIxn.Permissions)),
|
Permissions: make([]*envoy_rbac_v3.Permission, 0, len(rbacIxn.Permissions)),
|
||||||
}
|
}
|
||||||
for _, perm := range rbacIxn.Permissions {
|
for _, perm := range rbacIxn.Permissions {
|
||||||
|
@ -551,6 +603,11 @@ func makeRBACRules(
|
||||||
} else {
|
} else {
|
||||||
// For L4: we should generate one big Policy listing all Principals
|
// For L4: we should generate one big Policy listing all Principals
|
||||||
principalsL4 = append(principalsL4, rbacIxn.ComputedPrincipal)
|
principalsL4 = append(principalsL4, rbacIxn.ComputedPrincipal)
|
||||||
|
// Append JWT principals to list of principals
|
||||||
|
if len(infos) > 0 {
|
||||||
|
claimsPrincipal := jwtInfosToPrincipals(infos)
|
||||||
|
principalsL4 = combineBasePrincipalWithJWTPrincipals(principalsL4, claimsPrincipal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(principalsL4) > 0 {
|
if len(principalsL4) > 0 {
|
||||||
|
@ -566,6 +623,109 @@ func makeRBACRules(
|
||||||
return rbac
|
return rbac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// combineBasePrincipalWithJWTPrincipals ensure each RBAC/Network principal is associated with
|
||||||
|
// the JWT principal
|
||||||
|
func combineBasePrincipalWithJWTPrincipals(p []*envoy_rbac_v3.Principal, cp *envoy_rbac_v3.Principal) []*envoy_rbac_v3.Principal {
|
||||||
|
res := make([]*envoy_rbac_v3.Principal, 0)
|
||||||
|
|
||||||
|
for _, principal := range p {
|
||||||
|
if principal != nil && cp != nil {
|
||||||
|
p := andPrincipals([]*envoy_rbac_v3.Principal{principal, cp})
|
||||||
|
res = append(res, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectJWTInfos extracts all the collected JWTInfos top level infos
|
||||||
|
// and permission level infos and returns them as a single array
|
||||||
|
func collectJWTInfos(rbacIxn *rbacIntention) []*JWTInfo {
|
||||||
|
infos := make([]*JWTInfo, 0, len(rbacIxn.jwtInfos))
|
||||||
|
|
||||||
|
if len(rbacIxn.jwtInfos) > 0 {
|
||||||
|
infos = append(infos, rbacIxn.jwtInfos...)
|
||||||
|
}
|
||||||
|
for _, perm := range rbacIxn.Permissions {
|
||||||
|
infos = append(infos, perm.jwtInfos...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func jwtInfosToPrincipals(c []*JWTInfo) *envoy_rbac_v3.Principal {
|
||||||
|
ps := make([]*envoy_rbac_v3.Principal, 0)
|
||||||
|
|
||||||
|
for _, jwtInfo := range c {
|
||||||
|
if jwtInfo != nil {
|
||||||
|
for _, claim := range jwtInfo.Claims {
|
||||||
|
ps = append(ps, jwtClaimToPrincipal(claim, jwtInfo.MetadataPayloadKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return orPrincipals(ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwtClaimToPrincipal takes in a payloadkey which is the metadata key. This key is generated by using provider name,
|
||||||
|
// permission index with a jwt_payload prefix. See buildPayloadInMetadataKey in agent/xds/jwt_authn.go
|
||||||
|
//
|
||||||
|
// This uniquely generated payloadKey is the first segment in the path to validate the JWT claims. The subsequent segments
|
||||||
|
// come from the Path included in the IntentionJWTClaimVerification param.
|
||||||
|
func jwtClaimToPrincipal(c *structs.IntentionJWTClaimVerification, payloadKey string) *envoy_rbac_v3.Principal {
|
||||||
|
segments := pathToSegments(c.Path, payloadKey)
|
||||||
|
|
||||||
|
return &envoy_rbac_v3.Principal{
|
||||||
|
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
||||||
|
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
||||||
|
Filter: jwtEnvoyFilter,
|
||||||
|
Path: segments,
|
||||||
|
Value: &envoy_matcher_v3.ValueMatcher{
|
||||||
|
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
||||||
|
StringMatch: &envoy_matcher_v3.StringMatcher{
|
||||||
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
||||||
|
Exact: c.Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pathToSegments generates an array of MetadataMatcher_PathSegment that starts with the payloadkey
|
||||||
|
// and is followed by all existing strings in the path.
|
||||||
|
//
|
||||||
|
// eg. calling: pathToSegments([]string{"perms", "roles"}, "jwt_payload_okta") should return the following:
|
||||||
|
//
|
||||||
|
// []*envoy_matcher_v3.MetadataMatcher_PathSegment{
|
||||||
|
// {
|
||||||
|
// Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "jwt_payload_okta"},
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "perms"},
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "roles"},
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
func pathToSegments(paths []string, payloadKey string) []*envoy_matcher_v3.MetadataMatcher_PathSegment {
|
||||||
|
|
||||||
|
segments := make([]*envoy_matcher_v3.MetadataMatcher_PathSegment, 0, len(paths))
|
||||||
|
segments = append(segments, makeSegment(payloadKey))
|
||||||
|
|
||||||
|
for _, p := range paths {
|
||||||
|
segments = append(segments, makeSegment(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
return segments
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSegment(key string) *envoy_matcher_v3.MetadataMatcher_PathSegment {
|
||||||
|
return &envoy_matcher_v3.MetadataMatcher_PathSegment{
|
||||||
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: key},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func optimizePrincipals(orig []*envoy_rbac_v3.Principal) []*envoy_rbac_v3.Principal {
|
func optimizePrincipals(orig []*envoy_rbac_v3.Principal) []*envoy_rbac_v3.Principal {
|
||||||
// If they are all ORs, then OR them together.
|
// If they are all ORs, then OR them together.
|
||||||
var orIds []*envoy_rbac_v3.Principal
|
var orIds []*envoy_rbac_v3.Principal
|
||||||
|
|
|
@ -484,6 +484,17 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
||||||
ixn.Permissions = perms
|
ixn.Permissions = perms
|
||||||
return ixn
|
return ixn
|
||||||
}
|
}
|
||||||
|
testIntentionWithJWT := func(src string, action structs.IntentionAction, jwt *structs.IntentionJWTRequirement, perms ...*structs.IntentionPermission) *structs.Intention {
|
||||||
|
ixn := testIntention(t, src, "api", action)
|
||||||
|
ixn.JWT = jwt
|
||||||
|
ixn.Action = action
|
||||||
|
if perms != nil {
|
||||||
|
ixn.Permissions = perms
|
||||||
|
ixn.Action = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return ixn
|
||||||
|
}
|
||||||
testPeerTrustBundle := []*pbpeering.PeeringTrustBundle{
|
testPeerTrustBundle := []*pbpeering.PeeringTrustBundle{
|
||||||
{
|
{
|
||||||
PeerName: "peer1",
|
PeerName: "peer1",
|
||||||
|
@ -506,6 +517,28 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
||||||
PathPrefix: "/",
|
PathPrefix: "/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
oktaWithClaims = structs.IntentionJWTProvider{
|
||||||
|
Name: "okta",
|
||||||
|
VerifyClaims: []*structs.IntentionJWTClaimVerification{
|
||||||
|
{Path: []string{"roles"}, Value: "testing"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
auth0WithClaims = structs.IntentionJWTProvider{
|
||||||
|
Name: "auth0",
|
||||||
|
VerifyClaims: []*structs.IntentionJWTClaimVerification{
|
||||||
|
{Path: []string{"perms", "role"}, Value: "admin"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
jwtRequirement = &structs.IntentionJWTRequirement{
|
||||||
|
Providers: []*structs.IntentionJWTProvider{
|
||||||
|
&oktaWithClaims,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
auth0Requirement = &structs.IntentionJWTRequirement{
|
||||||
|
Providers: []*structs.IntentionJWTProvider{
|
||||||
|
&auth0WithClaims,
|
||||||
|
},
|
||||||
|
}
|
||||||
permDenySlashPrefix = &structs.IntentionPermission{
|
permDenySlashPrefix = &structs.IntentionPermission{
|
||||||
Action: structs.IntentionActionDeny,
|
Action: structs.IntentionActionDeny,
|
||||||
HTTP: &structs.IntentionHTTPPermission{
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
|
@ -804,6 +837,70 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
// ========= JWTAuthn Filter checks
|
||||||
|
"top-level-jwt-no-permissions": {
|
||||||
|
intentionDefaultAllow: false,
|
||||||
|
intentions: sorted(
|
||||||
|
testIntentionWithJWT("web", structs.IntentionActionAllow, jwtRequirement),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"empty-top-level-jwt-with-one-permission": {
|
||||||
|
intentionDefaultAllow: false,
|
||||||
|
intentions: sorted(
|
||||||
|
testIntentionWithJWT("web", structs.IntentionActionAllow, nil, &structs.IntentionPermission{
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
|
PathPrefix: "some-path",
|
||||||
|
},
|
||||||
|
JWT: jwtRequirement,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"top-level-jwt-with-one-permission": {
|
||||||
|
intentionDefaultAllow: false,
|
||||||
|
intentions: sorted(
|
||||||
|
testIntentionWithJWT("web",
|
||||||
|
structs.IntentionActionAllow,
|
||||||
|
jwtRequirement,
|
||||||
|
&structs.IntentionPermission{
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
|
PathExact: "/v1/secret",
|
||||||
|
},
|
||||||
|
JWT: auth0Requirement,
|
||||||
|
},
|
||||||
|
&structs.IntentionPermission{
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
|
PathExact: "/v1/admin",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"top-level-jwt-with-multiple-permissions": {
|
||||||
|
intentionDefaultAllow: false,
|
||||||
|
intentions: sorted(
|
||||||
|
testIntentionWithJWT("web",
|
||||||
|
structs.IntentionActionAllow,
|
||||||
|
jwtRequirement,
|
||||||
|
&structs.IntentionPermission{
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
|
PathExact: "/v1/secret",
|
||||||
|
},
|
||||||
|
JWT: auth0Requirement,
|
||||||
|
},
|
||||||
|
&structs.IntentionPermission{
|
||||||
|
Action: structs.IntentionActionAllow,
|
||||||
|
HTTP: &structs.IntentionHTTPPermission{
|
||||||
|
PathExact: "/v1/admin",
|
||||||
|
},
|
||||||
|
JWT: auth0Requirement,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
testLocalInfo := rbacLocalInfo{
|
testLocalInfo := rbacLocalInfo{
|
||||||
|
@ -1060,3 +1157,142 @@ func TestSpiffeMatcher(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathToSegments(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
key string
|
||||||
|
paths []string
|
||||||
|
expected []*envoy_matcher_v3.MetadataMatcher_PathSegment
|
||||||
|
}{
|
||||||
|
"single-path": {
|
||||||
|
key: "jwt_payload_okta",
|
||||||
|
paths: []string{"perms"},
|
||||||
|
expected: []*envoy_matcher_v3.MetadataMatcher_PathSegment{
|
||||||
|
{
|
||||||
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "jwt_payload_okta"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "perms"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multi-paths": {
|
||||||
|
key: "jwt_payload_okta",
|
||||||
|
paths: []string{"perms", "roles"},
|
||||||
|
expected: []*envoy_matcher_v3.MetadataMatcher_PathSegment{
|
||||||
|
{
|
||||||
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "jwt_payload_okta"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "perms"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Segment: &envoy_matcher_v3.MetadataMatcher_PathSegment_Key{Key: "roles"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
segments := pathToSegments(tt.paths, tt.key)
|
||||||
|
require.ElementsMatch(t, segments, tt.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJwtClaimToPrincipal(t *testing.T) {
|
||||||
|
var (
|
||||||
|
firstClaim = structs.IntentionJWTClaimVerification{
|
||||||
|
Path: []string{"perms"},
|
||||||
|
Value: "admin",
|
||||||
|
}
|
||||||
|
secondClaim = structs.IntentionJWTClaimVerification{
|
||||||
|
Path: []string{"passage"},
|
||||||
|
Value: "secret",
|
||||||
|
}
|
||||||
|
payloadKey = "dummy-key"
|
||||||
|
firstPrincipal = envoy_rbac_v3.Principal{
|
||||||
|
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
||||||
|
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
||||||
|
Filter: jwtEnvoyFilter,
|
||||||
|
Path: pathToSegments(firstClaim.Path, payloadKey),
|
||||||
|
Value: &envoy_matcher_v3.ValueMatcher{
|
||||||
|
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
||||||
|
StringMatch: &envoy_matcher_v3.StringMatcher{
|
||||||
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
||||||
|
Exact: firstClaim.Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
secondPrincipal = envoy_rbac_v3.Principal{
|
||||||
|
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
||||||
|
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
||||||
|
Filter: jwtEnvoyFilter,
|
||||||
|
Path: pathToSegments(secondClaim.Path, "second-key"),
|
||||||
|
Value: &envoy_matcher_v3.ValueMatcher{
|
||||||
|
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
||||||
|
StringMatch: &envoy_matcher_v3.StringMatcher{
|
||||||
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
||||||
|
Exact: secondClaim.Value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
tests := map[string]struct {
|
||||||
|
jwtInfos []*JWTInfo
|
||||||
|
expected *envoy_rbac_v3.Principal
|
||||||
|
}{
|
||||||
|
"single-jwt-info": {
|
||||||
|
jwtInfos: []*JWTInfo{
|
||||||
|
{
|
||||||
|
Claims: []*structs.IntentionJWTClaimVerification{&firstClaim},
|
||||||
|
MetadataPayloadKey: payloadKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &envoy_rbac_v3.Principal{
|
||||||
|
Identifier: &envoy_rbac_v3.Principal_OrIds{
|
||||||
|
OrIds: &envoy_rbac_v3.Principal_Set{
|
||||||
|
Ids: []*envoy_rbac_v3.Principal{&firstPrincipal},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"multiple-jwt-info": {
|
||||||
|
jwtInfos: []*JWTInfo{
|
||||||
|
{
|
||||||
|
Claims: []*structs.IntentionJWTClaimVerification{&firstClaim},
|
||||||
|
MetadataPayloadKey: payloadKey,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Claims: []*structs.IntentionJWTClaimVerification{&secondClaim},
|
||||||
|
MetadataPayloadKey: "second-key",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &envoy_rbac_v3.Principal{
|
||||||
|
Identifier: &envoy_rbac_v3.Principal_OrIds{
|
||||||
|
OrIds: &envoy_rbac_v3.Principal_Set{
|
||||||
|
Ids: []*envoy_rbac_v3.Principal{&firstPrincipal, &secondPrincipal},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
principal := jwtInfosToPrincipals(tt.jwtInfos)
|
||||||
|
require.Equal(t, principal, tt.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
"typedConfig": {
|
"typedConfig": {
|
||||||
"@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication",
|
||||||
"providers": {
|
"providers": {
|
||||||
"okta": {
|
"okta_0": {
|
||||||
"issuer": "test-issuer",
|
"issuer": "test-issuer",
|
||||||
|
"payloadInMetadata": "jwt_payload_okta_0",
|
||||||
"remoteJwks": {
|
"remoteJwks": {
|
||||||
"httpUri": {
|
"httpUri": {
|
||||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||||
|
@ -20,10 +21,10 @@
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"prefix": "/some-special-path"
|
"prefix": "some-special-path"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"providerName": "okta"
|
"providerName": "okta_0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"providers": {
|
"providers": {
|
||||||
"okta": {
|
"okta": {
|
||||||
"issuer": "test-issuer",
|
"issuer": "test-issuer",
|
||||||
|
"payloadInMetadata": "jwt_payload_okta",
|
||||||
"localJwks": {
|
"localJwks": {
|
||||||
"inlineString": "{\"keys\": [{\n \"crv\": \"P-256\",\n \"key_ops\": [\n \"verify\"\n ],\n \"kty\": \"EC\",\n \"x\": \"Wc9uZuPaB7Kh2FMc9wtJjRe8XD4yT2AYNAAkrYbVjuw\",\n \"y\": \"68hRTJiJNOwtrh4EoPXeVnRuH7hiSDJ_lmbbjfDfWq0\",\n \"alg\": \"ES256\",\n \"use\": \"sig\",\n \"kid\": \"ac1e8f90eddf61c429c61ca05b4f2e07\"\n}]}"
|
"inlineString": "{\"keys\": [{\n \"crv\": \"P-256\",\n \"key_ops\": [\n \"verify\"\n ],\n \"kty\": \"EC\",\n \"x\": \"Wc9uZuPaB7Kh2FMc9wtJjRe8XD4yT2AYNAAkrYbVjuw\",\n \"y\": \"68hRTJiJNOwtrh4EoPXeVnRuH7hiSDJ_lmbbjfDfWq0\",\n \"alg\": \"ES256\",\n \"use\": \"sig\",\n \"kid\": \"ac1e8f90eddf61c429c61ca05b4f2e07\"\n}]}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,21 @@
|
||||||
"providers": {
|
"providers": {
|
||||||
"okta": {
|
"okta": {
|
||||||
"issuer": "test-issuer",
|
"issuer": "test-issuer",
|
||||||
|
"payloadInMetadata": "jwt_payload_okta",
|
||||||
|
"remoteJwks": {
|
||||||
|
"httpUri": {
|
||||||
|
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||||
|
"cluster": "jwks_cluster",
|
||||||
|
"timeout": "1s"
|
||||||
|
},
|
||||||
|
"asyncFetch": {
|
||||||
|
"fastListener": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"okta_0": {
|
||||||
|
"issuer": "test-issuer",
|
||||||
|
"payloadInMetadata": "jwt_payload_okta_0",
|
||||||
"remoteJwks": {
|
"remoteJwks": {
|
||||||
"httpUri": {
|
"httpUri": {
|
||||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||||
|
@ -18,6 +33,7 @@
|
||||||
},
|
},
|
||||||
"auth0": {
|
"auth0": {
|
||||||
"issuer": "another-issuer",
|
"issuer": "another-issuer",
|
||||||
|
"payloadInMetadata": "jwt_payload_auth0",
|
||||||
"remoteJwks": {
|
"remoteJwks": {
|
||||||
"httpUri": {
|
"httpUri": {
|
||||||
"uri": "https://example-auth0.com/.well-known/jwks.json",
|
"uri": "https://example-auth0.com/.well-known/jwks.json",
|
||||||
|
@ -33,10 +49,10 @@
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"prefix": "/some-special-path"
|
"prefix": "some-special-path"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"providerName": "okta"
|
"providerName": "okta_0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"providers": {
|
"providers": {
|
||||||
"okta": {
|
"okta": {
|
||||||
"issuer": "test-issuer",
|
"issuer": "test-issuer",
|
||||||
|
"payloadInMetadata": "jwt_payload_okta",
|
||||||
"remoteJwks": {
|
"remoteJwks": {
|
||||||
"httpUri": {
|
"httpUri": {
|
||||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||||
|
|
|
@ -5,6 +5,21 @@
|
||||||
"providers": {
|
"providers": {
|
||||||
"okta": {
|
"okta": {
|
||||||
"issuer": "test-issuer",
|
"issuer": "test-issuer",
|
||||||
|
"payloadInMetadata": "jwt_payload_okta",
|
||||||
|
"remoteJwks": {
|
||||||
|
"httpUri": {
|
||||||
|
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||||
|
"cluster": "jwks_cluster",
|
||||||
|
"timeout": "1s"
|
||||||
|
},
|
||||||
|
"asyncFetch": {
|
||||||
|
"fastListener": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"okta_0": {
|
||||||
|
"issuer": "test-issuer",
|
||||||
|
"payloadInMetadata": "jwt_payload_okta_0",
|
||||||
"remoteJwks": {
|
"remoteJwks": {
|
||||||
"httpUri": {
|
"httpUri": {
|
||||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||||
|
@ -20,10 +35,10 @@
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"prefix": "/some-special-path"
|
"prefix": "some-special-path"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"providerName": "okta"
|
"providerName": "okta_0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
59
agent/xds/testdata/rbac/empty-top-level-jwt-with-one-permission--httpfilter.golden
vendored
Normal file
59
agent/xds/testdata/rbac/empty-top-level-jwt-with-one-permission--httpfilter.golden
vendored
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.http.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
"policies": {
|
||||||
|
"consul-intentions-layer7-0": {
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"urlPath": {
|
||||||
|
"path": {
|
||||||
|
"prefix": "some-path"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"principals": [
|
||||||
|
{
|
||||||
|
"andIds": {
|
||||||
|
"ids": [
|
||||||
|
{
|
||||||
|
"authenticated": {
|
||||||
|
"principalName": {
|
||||||
|
"safeRegex": {
|
||||||
|
"googleRe2": {},
|
||||||
|
"regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orIds": {
|
||||||
|
"ids": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_okta_0"},
|
||||||
|
{"key": "roles"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "testing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"statPrefix": "connect_authz"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.http.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
"policies": {
|
||||||
|
"consul-intentions-layer4": {
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"any": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"principals": [
|
||||||
|
{
|
||||||
|
"andIds": {
|
||||||
|
"ids": [
|
||||||
|
{
|
||||||
|
"authenticated": {
|
||||||
|
"principalName": {
|
||||||
|
"safeRegex": {
|
||||||
|
"googleRe2": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orIds": {
|
||||||
|
"ids": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_okta"},
|
||||||
|
{"key": "roles"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "testing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
"policies": {
|
||||||
|
"consul-intentions-layer4": {
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"any": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"principals": [
|
||||||
|
{
|
||||||
|
"authenticated": {
|
||||||
|
"principalName": {
|
||||||
|
"safeRegex": {
|
||||||
|
"googleRe2": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"statPrefix": "connect_authz"
|
||||||
|
}
|
||||||
|
}
|
113
agent/xds/testdata/rbac/top-level-jwt-with-multiple-permissions--httpfilter.golden
vendored
Normal file
113
agent/xds/testdata/rbac/top-level-jwt-with-multiple-permissions--httpfilter.golden
vendored
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.http.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
"policies": {
|
||||||
|
"consul-intentions-layer7-0": {
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"urlPath": {
|
||||||
|
"path": {
|
||||||
|
"exact": "/v1/secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"urlPath": {
|
||||||
|
"path": {
|
||||||
|
"exact": "/v1/admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"notRule": {
|
||||||
|
"urlPath": {
|
||||||
|
"path": {
|
||||||
|
"exact": "/v1/secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"principals": [
|
||||||
|
{
|
||||||
|
"andIds": {
|
||||||
|
"ids": [
|
||||||
|
{
|
||||||
|
"authenticated": {
|
||||||
|
"principalName": {
|
||||||
|
"safeRegex": {
|
||||||
|
"googleRe2": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orIds": {
|
||||||
|
"ids": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_okta"},
|
||||||
|
{"key": "roles"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "testing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0_0"},
|
||||||
|
{"key": "perms"},
|
||||||
|
{"key": "role"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0_1"},
|
||||||
|
{"key": "perms"},
|
||||||
|
{"key": "role"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"statPrefix": "connect_authz"
|
||||||
|
}
|
||||||
|
}
|
98
agent/xds/testdata/rbac/top-level-jwt-with-one-permission--httpfilter.golden
vendored
Normal file
98
agent/xds/testdata/rbac/top-level-jwt-with-one-permission--httpfilter.golden
vendored
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.http.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
"policies": {
|
||||||
|
"consul-intentions-layer7-0": {
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"urlPath": {
|
||||||
|
"path": {
|
||||||
|
"exact": "/v1/secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"urlPath": {
|
||||||
|
"path": {
|
||||||
|
"exact": "/v1/admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"notRule": {
|
||||||
|
"urlPath": {
|
||||||
|
"path": {
|
||||||
|
"exact": "/v1/secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"principals": [
|
||||||
|
{
|
||||||
|
"andIds": {
|
||||||
|
"ids": [
|
||||||
|
{
|
||||||
|
"authenticated": {
|
||||||
|
"principalName": {
|
||||||
|
"safeRegex": {
|
||||||
|
"googleRe2": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"orIds": {
|
||||||
|
"ids": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_okta"},
|
||||||
|
{"key": "roles"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "testing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0_0"},
|
||||||
|
{"key": "perms"},
|
||||||
|
{"key": "role"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"statPrefix": "connect_authz"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue