[NET-3092] JWT Verify claims handling (#17452)

* [NET-3092] JWT Verify claims handling
This commit is contained in:
Ronald 2023-05-30 13:38:33 -04:00 committed by GitHub
parent 24cb491c89
commit e273c08fda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1035 additions and 91 deletions

3
.changelog/17452.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
mesh: Support configuring JWT authentication in Envoy.
```

View File

@ -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),
}, },
}, },
}, },

View File

@ -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)
}) })
} }

View File

@ -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

View File

@ -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)
})
}
}

View File

@ -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"
} }
} }
] ]

View File

@ -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}]}"
} }

View File

@ -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"
} }
}, },
{ {

View File

@ -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",

View File

@ -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"
} }
}, },
{ {

View 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"
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
}

View File

@ -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"
}
}

View 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"
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
}

View File

@ -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"
}
}

View 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"
}
}
}
}
]
}
}
]
}
}
]
}
}
}
}
}

View File

@ -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"
}
}