mirror of https://github.com/hashicorp/consul
Use JWT-auth filter in metadata mode & Delegate validation to RBAC filter
parent
e7194787a7
commit
70536f5a38
|
@ -13,6 +13,7 @@ import (
|
||||||
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
envoy_http_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
"google.golang.org/protobuf/types/known/emptypb"
|
||||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,129 +23,149 @@ const (
|
||||||
jwksClusterPrefix = "jwks_cluster"
|
jwksClusterPrefix = "jwks_cluster"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is an intermediate JWTProvider form used to associate
|
// makeJWTAuthFilter builds jwt filter for envoy. It limits its use to referenced provider rather than every provider.
|
||||||
// unique payload keys to providers
|
//
|
||||||
type jwtAuthnProvider struct {
|
// Eg. If you have three providers: okta, auth0 and fusionAuth and only okta is referenced in your intentions, then this
|
||||||
ComputedName string
|
// will create a jwt-auth filter containing just okta in the list of providers.
|
||||||
Provider *structs.IntentionJWTProvider
|
func makeJWTAuthFilter(providerMap 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 jwtRequirements []*envoy_http_jwt_authn_v3.JwtRequirement
|
||||||
|
|
||||||
for _, intention := range intentions {
|
for _, intention := range intentions {
|
||||||
if intention.JWT == nil && !hasJWTconfig(intention.Permissions) {
|
if intention.JWT == nil && !hasJWTconfig(intention.Permissions) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, jwtReq := range collectJWTAuthnProviders(intention) {
|
for _, p := range collectJWTProviders(intention) {
|
||||||
if _, ok := providers[jwtReq.ComputedName]; ok {
|
providerName := p.Name
|
||||||
|
if _, ok := providers[providerName]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtProvider, ok := pCE[jwtReq.Provider.Name]
|
providerCE, ok := providerMap[providerName]
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("provider specified in intention does not exist. Provider name: %s", jwtReq.Provider.Name)
|
return nil, fmt.Errorf("provider specified in intention does not exist. Provider name: %s", providerName)
|
||||||
}
|
}
|
||||||
// If intention permissions use HTTP-match criteria with
|
|
||||||
// VerifyClaims, then generate a clone of the jwt provider with a
|
envoyCfg, err := buildJWTProviderConfig(providerCE)
|
||||||
// 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.ComputedName] = envoyCfg
|
providers[providerName] = envoyCfg
|
||||||
}
|
reqs := providerToJWTRequirement(providerCE)
|
||||||
|
jwtRequirements = append(jwtRequirements, reqs)
|
||||||
for k, perm := range intention.Permissions {
|
|
||||||
if perm.JWT == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, prov := range perm.JWT.Providers {
|
|
||||||
rule := buildRouteRule(prov, perm, "/", k)
|
|
||||||
rules = append(rules, rule)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if intention.JWT != nil {
|
if len(jwtRequirements) == 0 {
|
||||||
for _, provider := range intention.JWT.Providers {
|
//do not add jwt_authn filter when intentions don't have JWTs
|
||||||
// The top-level provider applies to all requests.
|
|
||||||
rule := buildRouteRule(provider, nil, "/", 0)
|
|
||||||
rules = append(rules, rule)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(intentions) == 0 && len(providers) == 0 {
|
|
||||||
//do not add jwt_authn filter when intentions don't have JWT
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := &envoy_http_jwt_authn_v3.JwtAuthentication{
|
cfg := &envoy_http_jwt_authn_v3.JwtAuthentication{
|
||||||
Providers: providers,
|
Providers: providers,
|
||||||
Rules: rules,
|
Rules: []*envoy_http_jwt_authn_v3.RequirementRule{
|
||||||
|
{
|
||||||
|
Match: &envoy_route_v3.RouteMatch{
|
||||||
|
PathSpecifier: &envoy_route_v3.RouteMatch_Prefix{Prefix: "/"},
|
||||||
|
},
|
||||||
|
RequirementType: makeJWTRequirementRule(andJWTRequirements(jwtRequirements)),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return makeEnvoyHTTPFilter(jwtEnvoyFilter, cfg)
|
return makeEnvoyHTTPFilter(jwtEnvoyFilter, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectJWTAuthnProviders(i *structs.Intention) []*jwtAuthnProvider {
|
func makeJWTRequirementRule(r *envoy_http_jwt_authn_v3.JwtRequirement) *envoy_http_jwt_authn_v3.RequirementRule_Requires {
|
||||||
var reqs []*jwtAuthnProvider
|
return &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
||||||
|
Requires: r,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// andJWTRequirements combines list of jwt requirements into a single jwt requirement.
|
||||||
|
func andJWTRequirements(reqs []*envoy_http_jwt_authn_v3.JwtRequirement) *envoy_http_jwt_authn_v3.JwtRequirement {
|
||||||
|
switch len(reqs) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return reqs[0]
|
||||||
|
default:
|
||||||
|
return &envoy_http_jwt_authn_v3.JwtRequirement{
|
||||||
|
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_RequiresAll{
|
||||||
|
RequiresAll: &envoy_http_jwt_authn_v3.JwtRequirementAndList{
|
||||||
|
Requirements: reqs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// providerToJWTRequirement builds the envoy jwtRequirement.
|
||||||
|
//
|
||||||
|
// Note: since the rbac filter is in charge of making decisions of allow/denied, this
|
||||||
|
// requirement uses `allow_missing_or_failed` to ensure it is always satisfied.
|
||||||
|
func providerToJWTRequirement(provider *structs.JWTProviderConfigEntry) *envoy_http_jwt_authn_v3.JwtRequirement {
|
||||||
|
return &envoy_http_jwt_authn_v3.JwtRequirement{
|
||||||
|
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_RequiresAny{
|
||||||
|
RequiresAny: &envoy_http_jwt_authn_v3.JwtRequirementOrList{
|
||||||
|
Requirements: []*envoy_http_jwt_authn_v3.JwtRequirement{
|
||||||
|
{
|
||||||
|
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
||||||
|
ProviderName: provider.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// We use allowMissingOrFailed to allow rbac filter to do the validation
|
||||||
|
{
|
||||||
|
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_AllowMissingOrFailed{
|
||||||
|
AllowMissingOrFailed: &emptypb.Empty{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectJWTProviders returns a list of all top level and permission level referenced providers.
|
||||||
|
func collectJWTProviders(i *structs.Intention) []*structs.IntentionJWTProvider {
|
||||||
|
// get permission level providers
|
||||||
|
reqs := getPermissionsProviders(i.Permissions)
|
||||||
|
|
||||||
if i.JWT != nil {
|
if i.JWT != nil {
|
||||||
for _, prov := range i.JWT.Providers {
|
// get top level providers
|
||||||
reqs = append(reqs, &jwtAuthnProvider{Provider: prov, ComputedName: makeComputedProviderName(prov.Name, nil, 0)})
|
reqs = append(reqs, i.JWT.Providers...)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
reqs = append(reqs, getPermissionsProviders(i.Permissions)...)
|
|
||||||
|
|
||||||
return reqs
|
return reqs
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPermissionsProviders(p []*structs.IntentionPermission) []*jwtAuthnProvider {
|
func getPermissionsProviders(perms []*structs.IntentionPermission) []*structs.IntentionJWTProvider {
|
||||||
var reqs []*jwtAuthnProvider
|
var reqs []*structs.IntentionJWTProvider
|
||||||
for k, perm := range p {
|
for _, p := range perms {
|
||||||
if perm.JWT == nil {
|
if p.JWT == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, prov := range perm.JWT.Providers {
|
|
||||||
reqs = append(reqs, &jwtAuthnProvider{Provider: prov, ComputedName: makeComputedProviderName(prov.Name, perm, k)})
|
reqs = append(reqs, p.JWT.Providers...)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return reqs
|
return reqs
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeComputedProviderName is used to create names for unique provider per permission
|
// buildPayloadInMetadataKey is used to create a unique payload key per provider.
|
||||||
// This is to stop jwt claims cross validation across permissions/providers.
|
// This is to ensure claims are validated/forwarded specifically under the right provider.
|
||||||
|
// The forwarded payload is used with other data (eg. service identity) by the RBAC filter
|
||||||
|
// to validate access to resource.
|
||||||
//
|
//
|
||||||
// eg. If Permission x is the 3rd permission and has a provider of original name okta
|
// eg. With a provider named okta will have a payload key of: jwt_payload_okta
|
||||||
// this function will return okta_3 as the computed provider name
|
func buildPayloadInMetadataKey(providerName string) string {
|
||||||
func makeComputedProviderName(name string, perm *structs.IntentionPermission, idx int) string {
|
return jwtMetadataKeyPrefix + "_" + providerName
|
||||||
if perm == nil {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s_%d", name, idx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildPayloadInMetadataKey is used to create a unique payload key per provider/permissions.
|
func buildJWTProviderConfig(p *structs.JWTProviderConfigEntry) (*envoy_http_jwt_authn_v3.JwtProvider, error) {
|
||||||
// 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),
|
PayloadInMetadata: buildPayloadInMetadataKey(p.Name),
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.Forwarding != nil {
|
if p.Forwarding != nil {
|
||||||
|
@ -262,43 +283,6 @@ func buildJWTRetryPolicy(r *structs.JWKSRetryPolicy) *envoy_core_v3.RetryPolicy
|
||||||
return &pol
|
return &pol
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
|
||||||
Match: &envoy_route_v3.RouteMatch{
|
|
||||||
PathSpecifier: &envoy_route_v3.RouteMatch_Prefix{Prefix: defaultPrefix},
|
|
||||||
},
|
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
|
||||||
ProviderName: makeComputedProviderName(provider.Name, perm, permIdx),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if perm != nil && perm.HTTP != nil {
|
|
||||||
if perm.HTTP.PathPrefix != "" {
|
|
||||||
rule.Match.PathSpecifier = &envoy_route_v3.RouteMatch_Prefix{
|
|
||||||
Prefix: perm.HTTP.PathPrefix,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if perm.HTTP.PathExact != "" {
|
|
||||||
rule.Match.PathSpecifier = &envoy_route_v3.RouteMatch_Path{
|
|
||||||
Path: perm.HTTP.PathExact,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if perm.HTTP.PathRegex != "" {
|
|
||||||
rule.Match.PathSpecifier = &envoy_route_v3.RouteMatch_SafeRegex{
|
|
||||||
SafeRegex: makeEnvoyRegexMatch(perm.HTTP.PathRegex),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rule
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasJWTconfig(p []*structs.IntentionPermission) bool {
|
func hasJWTconfig(p []*structs.IntentionPermission) bool {
|
||||||
for _, perm := range p {
|
for _, perm := range p {
|
||||||
if perm.JWT != nil {
|
if perm.JWT != nil {
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
||||||
envoy_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
||||||
envoy_http_jwt_authn_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3"
|
envoy_http_jwt_authn_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/jwt_authn/v3"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -173,6 +172,10 @@ func TestMakeJWTAUTHFilters(t *testing.T) {
|
||||||
intentions structs.SimplifiedIntentions
|
intentions structs.SimplifiedIntentions
|
||||||
provider map[string]*structs.JWTProviderConfigEntry
|
provider map[string]*structs.JWTProviderConfigEntry
|
||||||
}{
|
}{
|
||||||
|
"no-provider": {
|
||||||
|
intentions: simplified(makeTestIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow})),
|
||||||
|
provider: nil,
|
||||||
|
},
|
||||||
"remote-provider": {
|
"remote-provider": {
|
||||||
intentions: simplified(makeTestIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: oktaIntention})),
|
intentions: simplified(makeTestIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: oktaIntention})),
|
||||||
provider: remoteCE,
|
provider: remoteCE,
|
||||||
|
@ -206,123 +209,45 @@ func TestMakeJWTAUTHFilters(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMakeComputedProviderName(t *testing.T) {
|
func TestCollectJWTProviders(t *testing.T) {
|
||||||
tests := map[string]struct {
|
|
||||||
name string
|
|
||||||
perm *structs.IntentionPermission
|
|
||||||
idx int
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
"no-permissions": {
|
|
||||||
name: "okta",
|
|
||||||
idx: 0,
|
|
||||||
expected: "okta",
|
|
||||||
},
|
|
||||||
"exact-path-permission": {
|
|
||||||
name: "auth0",
|
|
||||||
perm: &structs.IntentionPermission{
|
|
||||||
HTTP: &structs.IntentionHTTPPermission{
|
|
||||||
PathExact: "admin",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
idx: 5,
|
|
||||||
expected: "auth0_5",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
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 {
|
tests := map[string]struct {
|
||||||
intention *structs.Intention
|
intention *structs.Intention
|
||||||
expected []*jwtAuthnProvider
|
expected []*structs.IntentionJWTProvider
|
||||||
}{
|
}{
|
||||||
"empty-top-level-jwt-and-empty-permissions": {
|
"empty-top-level-jwt-and-empty-permissions": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web"}),
|
intention: makeTestIntention(t, ixnOpts{src: "web"}),
|
||||||
expected: []*jwtAuthnProvider{},
|
expected: []*structs.IntentionJWTProvider{},
|
||||||
},
|
},
|
||||||
"top-level-jwt-and-empty-permissions": {
|
"top-level-jwt-and-empty-permissions": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: oktaIntention}),
|
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: oktaIntention}),
|
||||||
expected: []*jwtAuthnProvider{{Provider: &oktaProvider, ComputedName: oktaProvider.Name}},
|
expected: []*structs.IntentionJWTProvider{&oktaProvider},
|
||||||
},
|
},
|
||||||
"multi-top-level-jwt-and-empty-permissions": {
|
"multi-top-level-jwt-and-empty-permissions": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: multiProviderIntentions}),
|
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: multiProviderIntentions}),
|
||||||
expected: []*jwtAuthnProvider{
|
expected: []*structs.IntentionJWTProvider{&oktaProvider, &auth0Provider},
|
||||||
{Provider: &oktaProvider, ComputedName: oktaProvider.Name},
|
|
||||||
{Provider: &auth0Provider, ComputedName: auth0Provider.Name},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"top-level-jwt-and-one-jwt-permission": {
|
"top-level-jwt-and-one-jwt-permission": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: auth0Intention, perms: pWithOktaProvider}),
|
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: auth0Intention, perms: pWithOktaProvider}),
|
||||||
expected: []*jwtAuthnProvider{
|
expected: []*structs.IntentionJWTProvider{&auth0Provider, &oktaProvider},
|
||||||
{Provider: &auth0Provider, ComputedName: auth0Provider.Name},
|
|
||||||
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"top-level-jwt-and-multi-jwt-permissions": {
|
"top-level-jwt-and-multi-jwt-permissions": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: fakeIntention, perms: pWithMultiProviders}),
|
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: fakeIntention, perms: pWithMultiProviders}),
|
||||||
expected: []*jwtAuthnProvider{
|
expected: []*structs.IntentionJWTProvider{&fakeProvider, &oktaProvider, &auth0Provider},
|
||||||
{Provider: &fakeProvider, ComputedName: fakeProvider.Name},
|
|
||||||
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
|
||||||
{Provider: &auth0Provider, ComputedName: "auth0_0"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"empty-top-level-jwt-and-one-jwt-permission": {
|
"empty-top-level-jwt-and-one-jwt-permission": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithOktaProvider}),
|
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithOktaProvider}),
|
||||||
expected: []*jwtAuthnProvider{{Provider: &oktaProvider, ComputedName: "okta_0"}},
|
expected: []*structs.IntentionJWTProvider{&oktaProvider},
|
||||||
},
|
},
|
||||||
"empty-top-level-jwt-and-multi-jwt-permission": {
|
"empty-top-level-jwt-and-multi-jwt-permission": {
|
||||||
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithMultiProviders}),
|
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithMultiProviders}),
|
||||||
expected: []*jwtAuthnProvider{
|
expected: []*structs.IntentionJWTProvider{&oktaProvider, &auth0Provider},
|
||||||
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
|
||||||
{Provider: &auth0Provider, ComputedName: "auth0_0"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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 := collectJWTAuthnProviders(tt.intention)
|
reqs := collectJWTProviders(tt.intention)
|
||||||
require.ElementsMatch(t, reqs, tt.expected)
|
require.ElementsMatch(t, reqs, tt.expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -331,44 +256,36 @@ func TestCollectJWTAuthnProviders(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 []*jwtAuthnProvider
|
expected []*structs.IntentionJWTProvider
|
||||||
}{
|
}{
|
||||||
"empty-permissions": {
|
"empty-permissions": {
|
||||||
perms: []*structs.IntentionPermission{},
|
perms: []*structs.IntentionPermission{},
|
||||||
expected: []*jwtAuthnProvider{},
|
expected: []*structs.IntentionJWTProvider{},
|
||||||
},
|
},
|
||||||
"nil-permissions": {
|
"nil-permissions": {
|
||||||
perms: nil,
|
perms: nil,
|
||||||
expected: []*jwtAuthnProvider{},
|
expected: []*structs.IntentionJWTProvider{},
|
||||||
},
|
},
|
||||||
"permissions-with-no-jwt": {
|
"permissions-with-no-jwt": {
|
||||||
perms: []*structs.IntentionPermission{pWithNoJWT},
|
perms: []*structs.IntentionPermission{pWithNoJWT},
|
||||||
expected: []*jwtAuthnProvider{},
|
expected: []*structs.IntentionJWTProvider{},
|
||||||
},
|
},
|
||||||
"permissions-with-one-jwt": {
|
"permissions-with-one-jwt": {
|
||||||
perms: []*structs.IntentionPermission{pWithOktaProvider, pWithNoJWT},
|
perms: []*structs.IntentionPermission{pWithOktaProvider, pWithNoJWT},
|
||||||
expected: []*jwtAuthnProvider{
|
expected: []*structs.IntentionJWTProvider{&oktaProvider},
|
||||||
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"permissions-with-multiple-jwt": {
|
"permissions-with-multiple-jwt": {
|
||||||
perms: []*structs.IntentionPermission{pWithMultiProviders, pWithNoJWT},
|
perms: []*structs.IntentionPermission{pWithMultiProviders, pWithNoJWT},
|
||||||
expected: []*jwtAuthnProvider{
|
expected: []*structs.IntentionJWTProvider{&auth0Provider, &oktaProvider},
|
||||||
{Provider: &auth0Provider, ComputedName: "auth0_0"},
|
|
||||||
{Provider: &oktaProvider, ComputedName: "okta_0"},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
t.Run("getPermissionsProviders", func(t *testing.T) {
|
|
||||||
p := getPermissionsProviders(tt.perms)
|
p := getPermissionsProviders(tt.perms)
|
||||||
|
|
||||||
require.ElementsMatch(t, p, tt.expected)
|
require.ElementsMatch(t, p, tt.expected)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -415,7 +332,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),
|
PayloadInMetadata: buildPayloadInMetadataKey(ceRemoteJWKS.Name),
|
||||||
PadForwardPayloadHeader: false,
|
PadForwardPayloadHeader: false,
|
||||||
Forward: true,
|
Forward: true,
|
||||||
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_LocalJwks{
|
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_LocalJwks{
|
||||||
|
@ -433,7 +350,7 @@ func TestBuildJWTProviderConfig(t *testing.T) {
|
||||||
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),
|
PayloadInMetadata: buildPayloadInMetadataKey(ceRemoteJWKS.Name),
|
||||||
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{
|
||||||
|
@ -453,7 +370,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, tt.ce.GetName())
|
res, err := buildJWTProviderConfig(tt.ce)
|
||||||
|
|
||||||
if tt.expectedError != "" {
|
if tt.expectedError != "" {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
@ -620,104 +537,6 @@ func TestBuildJWTRetryPolicy(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildRouteRule(t *testing.T) {
|
|
||||||
var (
|
|
||||||
pWithExactPath = &structs.IntentionPermission{
|
|
||||||
Action: structs.IntentionActionAllow,
|
|
||||||
HTTP: &structs.IntentionHTTPPermission{
|
|
||||||
PathExact: "/exact-match",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pWithRegex = &structs.IntentionPermission{
|
|
||||||
Action: structs.IntentionActionAllow,
|
|
||||||
HTTP: &structs.IntentionHTTPPermission{
|
|
||||||
PathRegex: "p([a-z]+)ch",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
tests := map[string]struct {
|
|
||||||
provider *structs.IntentionJWTProvider
|
|
||||||
perm *structs.IntentionPermission
|
|
||||||
route string
|
|
||||||
expected *envoy_http_jwt_authn_v3.RequirementRule
|
|
||||||
}{
|
|
||||||
"permission-nil": {
|
|
||||||
provider: &oktaProvider,
|
|
||||||
perm: nil,
|
|
||||||
route: "/my-route",
|
|
||||||
expected: &envoy_http_jwt_authn_v3.RequirementRule{
|
|
||||||
Match: &envoy_route_v3.RouteMatch{PathSpecifier: &envoy_route_v3.RouteMatch_Prefix{Prefix: "/my-route"}},
|
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
|
||||||
ProviderName: oktaProvider.Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"permission-with-path-prefix": {
|
|
||||||
provider: &oktaProvider,
|
|
||||||
perm: pWithOktaProvider,
|
|
||||||
route: "/my-route",
|
|
||||||
expected: &envoy_http_jwt_authn_v3.RequirementRule{
|
|
||||||
Match: &envoy_route_v3.RouteMatch{PathSpecifier: &envoy_route_v3.RouteMatch_Prefix{
|
|
||||||
Prefix: pWithMultiProviders.HTTP.PathPrefix,
|
|
||||||
}},
|
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
|
||||||
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithMultiProviders, 0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"permission-with-exact-path": {
|
|
||||||
provider: &oktaProvider,
|
|
||||||
perm: pWithExactPath,
|
|
||||||
route: "/",
|
|
||||||
expected: &envoy_http_jwt_authn_v3.RequirementRule{
|
|
||||||
Match: &envoy_route_v3.RouteMatch{PathSpecifier: &envoy_route_v3.RouteMatch_Path{
|
|
||||||
Path: pWithExactPath.HTTP.PathExact,
|
|
||||||
}},
|
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
|
||||||
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithExactPath, 0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"permission-with-regex": {
|
|
||||||
provider: &oktaProvider,
|
|
||||||
perm: pWithRegex,
|
|
||||||
route: "/",
|
|
||||||
expected: &envoy_http_jwt_authn_v3.RequirementRule{
|
|
||||||
Match: &envoy_route_v3.RouteMatch{PathSpecifier: &envoy_route_v3.RouteMatch_SafeRegex{
|
|
||||||
SafeRegex: makeEnvoyRegexMatch(pWithRegex.HTTP.PathRegex),
|
|
||||||
}},
|
|
||||||
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
|
|
||||||
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
|
|
||||||
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
|
|
||||||
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithRegex, 0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
res := buildRouteRule(tt.provider, tt.perm, tt.route, 0)
|
|
||||||
require.Equal(t, res, tt.expected)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHasJWTconfig(t *testing.T) {
|
func TestHasJWTconfig(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
perms []*structs.IntentionPermission
|
perms []*structs.IntentionPermission
|
||||||
|
|
|
@ -1291,6 +1291,7 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
|
||||||
partition: cfgSnap.ProxyID.PartitionOrDefault(),
|
partition: cfgSnap.ProxyID.PartitionOrDefault(),
|
||||||
},
|
},
|
||||||
cfgSnap.ConnectProxy.InboundPeerTrustBundles,
|
cfgSnap.ConnectProxy.InboundPeerTrustBundles,
|
||||||
|
cfgSnap.JWTProviders,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1364,9 +1365,9 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
|
||||||
logger: s.Logger,
|
logger: s.Logger,
|
||||||
}
|
}
|
||||||
if useHTTPFilter {
|
if useHTTPFilter {
|
||||||
jwtFilter, jwtFilterErr := makeJWTAuthFilter(cfgSnap.JWTProviders, cfgSnap.ConnectProxy.Intentions)
|
jwtFilter, err := makeJWTAuthFilter(cfgSnap.JWTProviders, cfgSnap.ConnectProxy.Intentions)
|
||||||
if jwtFilterErr != nil {
|
if err != nil {
|
||||||
return nil, jwtFilterErr
|
return nil, err
|
||||||
}
|
}
|
||||||
rbacFilter, err := makeRBACHTTPFilter(
|
rbacFilter, err := makeRBACHTTPFilter(
|
||||||
cfgSnap.ConnectProxy.Intentions,
|
cfgSnap.ConnectProxy.Intentions,
|
||||||
|
@ -1377,6 +1378,7 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot
|
||||||
partition: cfgSnap.ProxyID.PartitionOrDefault(),
|
partition: cfgSnap.ProxyID.PartitionOrDefault(),
|
||||||
},
|
},
|
||||||
cfgSnap.ConnectProxy.InboundPeerTrustBundles,
|
cfgSnap.ConnectProxy.InboundPeerTrustBundles,
|
||||||
|
cfgSnap.JWTProviders,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1844,6 +1846,7 @@ func (s *ResourceGenerator) makeFilterChainTerminatingGateway(cfgSnap *proxycfg.
|
||||||
partition: cfgSnap.ProxyID.PartitionOrDefault(),
|
partition: cfgSnap.ProxyID.PartitionOrDefault(),
|
||||||
},
|
},
|
||||||
nil, // TODO(peering): verify intentions w peers don't apply to terminatingGateway
|
nil, // TODO(peering): verify intentions w peers don't apply to terminatingGateway
|
||||||
|
cfgSnap.JWTProviders,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -28,7 +28,10 @@ func makeRBACNetworkFilter(
|
||||||
localInfo rbacLocalInfo,
|
localInfo rbacLocalInfo,
|
||||||
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
||||||
) (*envoy_listener_v3.Filter, error) {
|
) (*envoy_listener_v3.Filter, error) {
|
||||||
rules := makeRBACRules(intentions, intentionDefaultAllow, localInfo, false, peerTrustBundles)
|
rules, err := makeRBACRules(intentions, intentionDefaultAllow, localInfo, false, peerTrustBundles, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cfg := &envoy_network_rbac_v3.RBAC{
|
cfg := &envoy_network_rbac_v3.RBAC{
|
||||||
StatPrefix: "connect_authz",
|
StatPrefix: "connect_authz",
|
||||||
|
@ -42,8 +45,12 @@ func makeRBACHTTPFilter(
|
||||||
intentionDefaultAllow bool,
|
intentionDefaultAllow bool,
|
||||||
localInfo rbacLocalInfo,
|
localInfo rbacLocalInfo,
|
||||||
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
||||||
|
providerMap map[string]*structs.JWTProviderConfigEntry,
|
||||||
) (*envoy_http_v3.HttpFilter, error) {
|
) (*envoy_http_v3.HttpFilter, error) {
|
||||||
rules := makeRBACRules(intentions, intentionDefaultAllow, localInfo, true, peerTrustBundles)
|
rules, err := makeRBACRules(intentions, intentionDefaultAllow, localInfo, true, peerTrustBundles, providerMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cfg := &envoy_http_rbac_v3.RBAC{
|
cfg := &envoy_http_rbac_v3.RBAC{
|
||||||
Rules: rules,
|
Rules: rules,
|
||||||
|
@ -56,7 +63,8 @@ func intentionListToIntermediateRBACForm(
|
||||||
localInfo rbacLocalInfo,
|
localInfo rbacLocalInfo,
|
||||||
isHTTP bool,
|
isHTTP bool,
|
||||||
trustBundlesByPeer map[string]*pbpeering.PeeringTrustBundle,
|
trustBundlesByPeer map[string]*pbpeering.PeeringTrustBundle,
|
||||||
) []*rbacIntention {
|
providerMap map[string]*structs.JWTProviderConfigEntry,
|
||||||
|
) ([]*rbacIntention, error) {
|
||||||
sort.Sort(structs.IntentionPrecedenceSorter(intentions))
|
sort.Sort(structs.IntentionPrecedenceSorter(intentions))
|
||||||
|
|
||||||
// Omit any lower-precedence intentions that share the same source.
|
// Omit any lower-precedence intentions that share the same source.
|
||||||
|
@ -73,10 +81,13 @@ func intentionListToIntermediateRBACForm(
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rixn := intentionToIntermediateRBACForm(ixn, localInfo, isHTTP, trustBundle)
|
rixn, err := intentionToIntermediateRBACForm(ixn, localInfo, isHTTP, trustBundle, providerMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
rbacIxns = append(rbacIxns, rixn)
|
rbacIxns = append(rbacIxns, rixn)
|
||||||
}
|
}
|
||||||
return rbacIxns
|
return rbacIxns, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeSourcePrecedence(rbacIxns []*rbacIntention, intentionDefaultAction intentionAction, localInfo rbacLocalInfo) []*rbacIntention {
|
func removeSourcePrecedence(rbacIxns []*rbacIntention, intentionDefaultAction intentionAction, localInfo rbacLocalInfo) []*rbacIntention {
|
||||||
|
@ -216,7 +227,8 @@ func intentionToIntermediateRBACForm(
|
||||||
localInfo rbacLocalInfo,
|
localInfo rbacLocalInfo,
|
||||||
isHTTP bool,
|
isHTTP bool,
|
||||||
bundle *pbpeering.PeeringTrustBundle,
|
bundle *pbpeering.PeeringTrustBundle,
|
||||||
) *rbacIntention {
|
providerMap map[string]*structs.JWTProviderConfigEntry,
|
||||||
|
) (*rbacIntention, error) {
|
||||||
rixn := &rbacIntention{
|
rixn := &rbacIntention{
|
||||||
Source: rbacService{
|
Source: rbacService{
|
||||||
ServiceName: ixn.SourceServiceName(),
|
ServiceName: ixn.SourceServiceName(),
|
||||||
|
@ -233,36 +245,41 @@ func intentionToIntermediateRBACForm(
|
||||||
}
|
}
|
||||||
|
|
||||||
if isHTTP && ixn.JWT != nil {
|
if isHTTP && ixn.JWT != nil {
|
||||||
var c []*JWTInfo
|
var jwts []*JWTInfo
|
||||||
for _, prov := range ixn.JWT.Providers {
|
for _, prov := range ixn.JWT.Providers {
|
||||||
if len(prov.VerifyClaims) > 0 {
|
jwtProvider, ok := providerMap[prov.Name]
|
||||||
c = append(c, makeJWTInfos(prov, nil, 0))
|
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("provider specified in intention does not exist. Provider name: %s", prov.Name)
|
||||||
}
|
}
|
||||||
|
jwts = append(jwts, newJWTInfo(prov, jwtProvider))
|
||||||
}
|
}
|
||||||
if len(c) > 0 {
|
|
||||||
rixn.jwtInfos = c
|
rixn.jwtInfos = jwts
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 k, perm := range ixn.Permissions {
|
for _, perm := range ixn.Permissions {
|
||||||
rbacPerm := 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 {
|
if perm.JWT != nil {
|
||||||
var c []*JWTInfo
|
var jwts []*JWTInfo
|
||||||
for _, prov := range perm.JWT.Providers {
|
for _, prov := range perm.JWT.Providers {
|
||||||
if len(prov.VerifyClaims) > 0 {
|
jwtProvider, ok := providerMap[prov.Name]
|
||||||
c = append(c, makeJWTInfos(prov, perm, k))
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("provider specified in intention does not exist. Provider name: %s", prov.Name)
|
||||||
}
|
}
|
||||||
|
jwts = append(jwts, newJWTInfo(prov, jwtProvider))
|
||||||
}
|
}
|
||||||
if len(c) > 0 {
|
if len(jwts) > 0 {
|
||||||
rbacPerm.jwtInfos = c
|
rbacPerm.jwtInfos = jwts
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rixn.Permissions = append(rixn.Permissions, &rbacPerm)
|
rixn.Permissions = append(rixn.Permissions, &rbacPerm)
|
||||||
|
@ -275,18 +292,24 @@ func intentionToIntermediateRBACForm(
|
||||||
rixn.Action = intentionActionFromString(ixn.Action)
|
rixn.Action = intentionActionFromString(ixn.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rixn
|
return rixn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeJWTInfos(p *structs.IntentionJWTProvider, perm *structs.IntentionPermission, permKey int) *JWTInfo {
|
func newJWTInfo(p *structs.IntentionJWTProvider, ce *structs.JWTProviderConfigEntry) *JWTInfo {
|
||||||
return &JWTInfo{Claims: p.VerifyClaims, MetadataPayloadKey: buildPayloadInMetadataKey(p.Name, perm, permKey)}
|
return &JWTInfo{
|
||||||
|
Provider: p,
|
||||||
|
Issuer: ce.Issuer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type intentionAction int
|
type intentionAction int
|
||||||
|
|
||||||
type JWTInfo struct {
|
type JWTInfo struct {
|
||||||
Claims []*structs.IntentionJWTClaimVerification
|
// Provider issuer
|
||||||
MetadataPayloadKey string
|
// this information is coming from the config entry
|
||||||
|
Issuer string
|
||||||
|
// Provider is the intention provider
|
||||||
|
Provider *structs.IntentionJWTProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -341,15 +364,14 @@ type rbacIntention struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rbacIntention) FlattenPrincipal(localInfo rbacLocalInfo) *envoy_rbac_v3.Principal {
|
func (r *rbacIntention) FlattenPrincipal(localInfo rbacLocalInfo) *envoy_rbac_v3.Principal {
|
||||||
|
var principal *envoy_rbac_v3.Principal
|
||||||
if !localInfo.expectXFCC {
|
if !localInfo.expectXFCC {
|
||||||
return r.flattenPrincipalFromCert()
|
principal = r.flattenPrincipalFromCert()
|
||||||
|
|
||||||
} else if r.Source.Peer == "" {
|
} else if r.Source.Peer == "" {
|
||||||
// NOTE: ixnSourceMatches should enforce that all of Source and NotSources
|
// NOTE: ixnSourceMatches should enforce that all of Source and NotSources
|
||||||
// are peered or not-peered, so we only need to look at the Source element.
|
// are peered or not-peered, so we only need to look at the Source element.
|
||||||
return r.flattenPrincipalFromCert() // intention is not relevant to peering
|
principal = r.flattenPrincipalFromCert() // intention is not relevant to peering
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// If this intention is an L7 peered one, then it is exclusively resolvable
|
// If this intention is an L7 peered one, then it is exclusively resolvable
|
||||||
// using XFCC, rather than the TLS SAN field.
|
// using XFCC, rather than the TLS SAN field.
|
||||||
fromXFCC := r.flattenPrincipalFromXFCC()
|
fromXFCC := r.flattenPrincipalFromXFCC()
|
||||||
|
@ -357,12 +379,19 @@ func (r *rbacIntention) FlattenPrincipal(localInfo rbacLocalInfo) *envoy_rbac_v3
|
||||||
// Use of the XFCC one is gated on coming directly from our own gateways.
|
// Use of the XFCC one is gated on coming directly from our own gateways.
|
||||||
gwIDPattern := makeSpiffeMeshGatewayPattern(localInfo.trustDomain, localInfo.partition)
|
gwIDPattern := makeSpiffeMeshGatewayPattern(localInfo.trustDomain, localInfo.partition)
|
||||||
|
|
||||||
return andPrincipals([]*envoy_rbac_v3.Principal{
|
principal = andPrincipals([]*envoy_rbac_v3.Principal{
|
||||||
authenticatedPatternPrincipal(gwIDPattern),
|
authenticatedPatternPrincipal(gwIDPattern),
|
||||||
fromXFCC,
|
fromXFCC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(r.jwtInfos) == 0 {
|
||||||
|
return principal
|
||||||
|
}
|
||||||
|
|
||||||
|
return addJWTPrincipal(principal, r.jwtInfos)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *rbacIntention) flattenPrincipalFromCert() *envoy_rbac_v3.Principal {
|
func (r *rbacIntention) flattenPrincipalFromCert() *envoy_rbac_v3.Principal {
|
||||||
r.NotSources = simplifyNotSourceSlice(r.NotSources)
|
r.NotSources = simplifyNotSourceSlice(r.NotSources)
|
||||||
|
|
||||||
|
@ -417,17 +446,47 @@ type rbacPermission struct {
|
||||||
ComputedPermission *envoy_rbac_v3.Permission
|
ComputedPermission *envoy_rbac_v3.Permission
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flatten ensure the permission rules, not-rules, and jwt validation rules are merged into a single computed permission.
|
||||||
|
//
|
||||||
|
// Details on JWTInfo section:
|
||||||
|
// For each JWTInfo (AKA provider required), this builds 1 single permission that validates that the jwt has
|
||||||
|
// the right issuer (`iss`) field and validates the claims (if any).
|
||||||
|
//
|
||||||
|
// After generating a single permission per info, it combines all the info permissions into a single OrPermission.
|
||||||
|
// This orPermission is then attached to initial computed permission for jwt payload and claims validation.
|
||||||
func (p *rbacPermission) Flatten() *envoy_rbac_v3.Permission {
|
func (p *rbacPermission) Flatten() *envoy_rbac_v3.Permission {
|
||||||
if len(p.NotPerms) == 0 {
|
computedPermission := p.Perm
|
||||||
return p.Perm
|
if len(p.NotPerms) == 0 && len(p.jwtInfos) == 0 {
|
||||||
|
return computedPermission
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(p.NotPerms) != 0 {
|
||||||
parts := make([]*envoy_rbac_v3.Permission, 0, len(p.NotPerms)+1)
|
parts := make([]*envoy_rbac_v3.Permission, 0, len(p.NotPerms)+1)
|
||||||
parts = append(parts, p.Perm)
|
parts = append(parts, p.Perm)
|
||||||
for _, notPerm := range p.NotPerms {
|
for _, notPerm := range p.NotPerms {
|
||||||
parts = append(parts, notPermission(notPerm))
|
parts = append(parts, notPermission(notPerm))
|
||||||
}
|
}
|
||||||
return andPermissions(parts)
|
computedPermission = andPermissions(parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.jwtInfos) == 0 {
|
||||||
|
return computedPermission
|
||||||
|
}
|
||||||
|
|
||||||
|
var jwtPerms []*envoy_rbac_v3.Permission
|
||||||
|
for _, info := range p.jwtInfos {
|
||||||
|
payloadKey := buildPayloadInMetadataKey(info.Provider.Name)
|
||||||
|
claimsPermission := jwtInfosToPermission(info.Provider.VerifyClaims, payloadKey)
|
||||||
|
issuerPermission := segmentToPermission(pathToSegments([]string{"iss"}, payloadKey), info.Issuer)
|
||||||
|
|
||||||
|
perm := andPermissions([]*envoy_rbac_v3.Permission{
|
||||||
|
issuerPermission, claimsPermission,
|
||||||
|
})
|
||||||
|
jwtPerms = append(jwtPerms, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtPerm := orPermissions(jwtPerms)
|
||||||
|
return andPermissions([]*envoy_rbac_v3.Permission{computedPermission, jwtPerm})
|
||||||
}
|
}
|
||||||
|
|
||||||
// simplifyNotSourceSlice will collapse NotSources elements together if any element is
|
// simplifyNotSourceSlice will collapse NotSources elements together if any element is
|
||||||
|
@ -526,7 +585,8 @@ func makeRBACRules(
|
||||||
localInfo rbacLocalInfo,
|
localInfo rbacLocalInfo,
|
||||||
isHTTP bool,
|
isHTTP bool,
|
||||||
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
peerTrustBundles []*pbpeering.PeeringTrustBundle,
|
||||||
) *envoy_rbac_v3.RBAC {
|
providerMap map[string]*structs.JWTProviderConfigEntry,
|
||||||
|
) (*envoy_rbac_v3.RBAC, error) {
|
||||||
// TODO(banks,rb): Implement revocation list checking?
|
// TODO(banks,rb): Implement revocation list checking?
|
||||||
|
|
||||||
// TODO(peering): mkeeler asked that these maps come from proxycfg instead of
|
// TODO(peering): mkeeler asked that these maps come from proxycfg instead of
|
||||||
|
@ -546,7 +606,10 @@ func makeRBACRules(
|
||||||
}
|
}
|
||||||
|
|
||||||
// First build up just the basic principal matches.
|
// First build up just the basic principal matches.
|
||||||
rbacIxns := intentionListToIntermediateRBACForm(intentions, localInfo, isHTTP, trustBundlesByPeer)
|
rbacIxns, err := intentionListToIntermediateRBACForm(intentions, localInfo, isHTTP, trustBundlesByPeer, providerMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize: if we are in default-deny then all intentions must be allows and vice versa
|
// Normalize: if we are in default-deny then all intentions must be allows and vice versa
|
||||||
intentionDefaultAction := intentionActionFromBool(intentionDefaultAllow)
|
intentionDefaultAction := intentionActionFromBool(intentionDefaultAllow)
|
||||||
|
@ -574,10 +637,6 @@ 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")
|
||||||
|
@ -587,10 +646,6 @@ func makeRBACRules(
|
||||||
}
|
}
|
||||||
|
|
||||||
rbacPrincipals := optimizePrincipals([]*envoy_rbac_v3.Principal{rbacIxn.ComputedPrincipal})
|
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: rbacPrincipals,
|
Principals: rbacPrincipals,
|
||||||
|
@ -603,11 +658,6 @@ 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 {
|
||||||
|
@ -620,59 +670,74 @@ func makeRBACRules(
|
||||||
if len(rbac.Policies) == 0 {
|
if len(rbac.Policies) == 0 {
|
||||||
rbac.Policies = nil
|
rbac.Policies = nil
|
||||||
}
|
}
|
||||||
return rbac
|
return rbac, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// combineBasePrincipalWithJWTPrincipals ensure each RBAC/Network principal is associated with
|
// addJWTPrincipal ensure the passed RBAC/Network principal is associated with
|
||||||
// the JWT principal
|
// a JWT principal when JWTs validation is required.
|
||||||
func combineBasePrincipalWithJWTPrincipals(p []*envoy_rbac_v3.Principal, cp *envoy_rbac_v3.Principal) []*envoy_rbac_v3.Principal {
|
//
|
||||||
res := make([]*envoy_rbac_v3.Principal, 0)
|
// For each jwtInfo, this builds a first principal that validates that the jwt has the right issuer (`iss`).
|
||||||
|
// It collects all the claims principal and combines them into a single principal using jwtClaimsToPrincipals.
|
||||||
|
// It then combines the issuer principal and the claims principal into a single principal.
|
||||||
|
//
|
||||||
|
// After generating a single principal per info, it combines all the info principals into a single jwt OrPrincipal.
|
||||||
|
// This orPrincipal is then attached to the RBAC/NETWORK principal for jwt payload validation.
|
||||||
|
func addJWTPrincipal(principal *envoy_rbac_v3.Principal, infos []*JWTInfo) *envoy_rbac_v3.Principal {
|
||||||
|
if len(infos) == 0 {
|
||||||
|
return principal
|
||||||
|
}
|
||||||
|
jwtPrincipals := make([]*envoy_rbac_v3.Principal, 0, len(infos))
|
||||||
|
for _, info := range infos {
|
||||||
|
payloadKey := buildPayloadInMetadataKey(info.Provider.Name)
|
||||||
|
|
||||||
for _, principal := range p {
|
// build jwt provider issuer principal
|
||||||
if principal != nil && cp != nil {
|
segments := pathToSegments([]string{"iss"}, payloadKey)
|
||||||
p := andPrincipals([]*envoy_rbac_v3.Principal{principal, cp})
|
p := segmentToPrincipal(segments, info.Issuer)
|
||||||
res = append(res, p)
|
|
||||||
|
// add jwt provider claims principal if any
|
||||||
|
if cp := jwtClaimsToPrincipals(info.Provider.VerifyClaims, payloadKey); cp != nil {
|
||||||
|
p = andPrincipals([]*envoy_rbac_v3.Principal{p, cp})
|
||||||
}
|
}
|
||||||
}
|
jwtPrincipals = append(jwtPrincipals, p)
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// collectJWTInfos extracts all the collected JWTInfos top level infos
|
// make jwt principals into 1 single principal
|
||||||
// and permission level infos and returns them as a single array
|
jwtFinalPrincipal := orPrincipals(jwtPrincipals)
|
||||||
func collectJWTInfos(rbacIxn *rbacIntention) []*JWTInfo {
|
|
||||||
infos := make([]*JWTInfo, 0, len(rbacIxn.jwtInfos))
|
|
||||||
|
|
||||||
if len(rbacIxn.jwtInfos) > 0 {
|
if principal == nil {
|
||||||
infos = append(infos, rbacIxn.jwtInfos...)
|
return jwtFinalPrincipal
|
||||||
}
|
|
||||||
for _, perm := range rbacIxn.Permissions {
|
|
||||||
infos = append(infos, perm.jwtInfos...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return infos
|
return andPrincipals([]*envoy_rbac_v3.Principal{principal, jwtFinalPrincipal})
|
||||||
}
|
}
|
||||||
|
|
||||||
func jwtInfosToPrincipals(c []*JWTInfo) *envoy_rbac_v3.Principal {
|
func jwtClaimsToPrincipals(claims []*structs.IntentionJWTClaimVerification, payloadkey string) *envoy_rbac_v3.Principal {
|
||||||
ps := make([]*envoy_rbac_v3.Principal, 0)
|
ps := make([]*envoy_rbac_v3.Principal, 0)
|
||||||
|
|
||||||
for _, jwtInfo := range c {
|
for _, claim := range claims {
|
||||||
if jwtInfo != nil {
|
ps = append(ps, jwtClaimToPrincipal(claim, payloadkey))
|
||||||
for _, claim := range jwtInfo.Claims {
|
|
||||||
ps = append(ps, jwtClaimToPrincipal(claim, jwtInfo.MetadataPayloadKey))
|
|
||||||
}
|
}
|
||||||
|
switch len(ps) {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return ps[0]
|
||||||
|
default:
|
||||||
|
return andPrincipals(ps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return orPrincipals(ps)
|
|
||||||
}
|
|
||||||
|
|
||||||
// jwtClaimToPrincipal takes in a payloadkey which is the metadata key. This key is generated by using provider name,
|
// 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
|
// and 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
|
// 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.
|
// come from the Path included in the IntentionJWTClaimVerification param.
|
||||||
func jwtClaimToPrincipal(c *structs.IntentionJWTClaimVerification, payloadKey string) *envoy_rbac_v3.Principal {
|
func jwtClaimToPrincipal(c *structs.IntentionJWTClaimVerification, payloadKey string) *envoy_rbac_v3.Principal {
|
||||||
segments := pathToSegments(c.Path, payloadKey)
|
segments := pathToSegments(c.Path, payloadKey)
|
||||||
|
return segmentToPrincipal(segments, c.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func segmentToPrincipal(segments []*envoy_matcher_v3.MetadataMatcher_PathSegment, v string) *envoy_rbac_v3.Principal {
|
||||||
return &envoy_rbac_v3.Principal{
|
return &envoy_rbac_v3.Principal{
|
||||||
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
||||||
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
||||||
|
@ -682,7 +747,41 @@ func jwtClaimToPrincipal(c *structs.IntentionJWTClaimVerification, payloadKey st
|
||||||
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
||||||
StringMatch: &envoy_matcher_v3.StringMatcher{
|
StringMatch: &envoy_matcher_v3.StringMatcher{
|
||||||
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
|
||||||
Exact: c.Value,
|
Exact: v,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jwtInfosToPermission(claims []*structs.IntentionJWTClaimVerification, payloadkey string) *envoy_rbac_v3.Permission {
|
||||||
|
ps := make([]*envoy_rbac_v3.Permission, 0, len(claims))
|
||||||
|
|
||||||
|
for _, claim := range claims {
|
||||||
|
ps = append(ps, jwtClaimToPermission(claim, payloadkey))
|
||||||
|
}
|
||||||
|
return andPermissions(ps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jwtClaimToPermission(c *structs.IntentionJWTClaimVerification, payloadKey string) *envoy_rbac_v3.Permission {
|
||||||
|
segments := pathToSegments(c.Path, payloadKey)
|
||||||
|
return segmentToPermission(segments, c.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func segmentToPermission(segments []*envoy_matcher_v3.MetadataMatcher_PathSegment, v string) *envoy_rbac_v3.Permission {
|
||||||
|
return &envoy_rbac_v3.Permission{
|
||||||
|
Rule: &envoy_rbac_v3.Permission_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: v,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -837,6 +936,10 @@ func countWild(src rbacService) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func andPrincipals(ids []*envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
func andPrincipals(ids []*envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
||||||
|
switch len(ids) {
|
||||||
|
case 1:
|
||||||
|
return ids[0]
|
||||||
|
default:
|
||||||
return &envoy_rbac_v3.Principal{
|
return &envoy_rbac_v3.Principal{
|
||||||
Identifier: &envoy_rbac_v3.Principal_AndIds{
|
Identifier: &envoy_rbac_v3.Principal_AndIds{
|
||||||
AndIds: &envoy_rbac_v3.Principal_Set{
|
AndIds: &envoy_rbac_v3.Principal_Set{
|
||||||
|
@ -845,8 +948,13 @@ func andPrincipals(ids []*envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func orPrincipals(ids []*envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
func orPrincipals(ids []*envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
||||||
|
switch len(ids) {
|
||||||
|
case 1:
|
||||||
|
return ids[0]
|
||||||
|
default:
|
||||||
return &envoy_rbac_v3.Principal{
|
return &envoy_rbac_v3.Principal{
|
||||||
Identifier: &envoy_rbac_v3.Principal_OrIds{
|
Identifier: &envoy_rbac_v3.Principal_OrIds{
|
||||||
OrIds: &envoy_rbac_v3.Principal_Set{
|
OrIds: &envoy_rbac_v3.Principal_Set{
|
||||||
|
@ -855,6 +963,7 @@ func orPrincipals(ids []*envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func notPrincipal(id *envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
func notPrincipal(id *envoy_rbac_v3.Principal) *envoy_rbac_v3.Principal {
|
||||||
return &envoy_rbac_v3.Principal{
|
return &envoy_rbac_v3.Principal{
|
||||||
|
@ -1206,3 +1315,20 @@ func andPermissions(perms []*envoy_rbac_v3.Permission) *envoy_rbac_v3.Permission
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func orPermissions(perms []*envoy_rbac_v3.Permission) *envoy_rbac_v3.Permission {
|
||||||
|
switch len(perms) {
|
||||||
|
case 0:
|
||||||
|
return anyPermission()
|
||||||
|
case 1:
|
||||||
|
return perms[0]
|
||||||
|
default:
|
||||||
|
return &envoy_rbac_v3.Permission{
|
||||||
|
Rule: &envoy_rbac_v3.Permission_OrRules{
|
||||||
|
OrRules: &envoy_rbac_v3.Permission_Set{
|
||||||
|
Rules: perms,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -451,10 +451,11 @@ func TestRemoveIntentionPrecedence(t *testing.T) {
|
||||||
|
|
||||||
for name, tt := range tests {
|
for name, tt := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
rbacIxns := intentionListToIntermediateRBACForm(tt.intentions, testLocalInfo, tt.http, testPeerTrustBundle)
|
rbacIxns, err := intentionListToIntermediateRBACForm(tt.intentions, testLocalInfo, tt.http, testPeerTrustBundle, nil)
|
||||||
intentionDefaultAction := intentionActionFromBool(tt.intentionDefaultAllow)
|
intentionDefaultAction := intentionActionFromBool(tt.intentionDefaultAllow)
|
||||||
rbacIxns = removeIntentionPrecedence(rbacIxns, intentionDefaultAction, testLocalInfo)
|
rbacIxns = removeIntentionPrecedence(rbacIxns, intentionDefaultAction, testLocalInfo)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
require.Equal(t, tt.expect, rbacIxns)
|
require.Equal(t, tt.expect, rbacIxns)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -529,6 +530,10 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
||||||
{Path: []string{"perms", "role"}, Value: "admin"},
|
{Path: []string{"perms", "role"}, Value: "admin"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
testJWTProviderConfigEntry = map[string]*structs.JWTProviderConfigEntry{
|
||||||
|
"okta": {Name: "okta", Issuer: "mytest.okta-issuer"},
|
||||||
|
"auth0": {Name: "auth0", Issuer: "mytest.auth0-issuer"},
|
||||||
|
}
|
||||||
jwtRequirement = &structs.IntentionJWTRequirement{
|
jwtRequirement = &structs.IntentionJWTRequirement{
|
||||||
Providers: []*structs.IntentionJWTProvider{
|
Providers: []*structs.IntentionJWTProvider{
|
||||||
&oktaWithClaims,
|
&oktaWithClaims,
|
||||||
|
@ -922,7 +927,7 @@ func TestMakeRBACNetworkAndHTTPFilters(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
t.Run("http filter", func(t *testing.T) {
|
t.Run("http filter", func(t *testing.T) {
|
||||||
filter, err := makeRBACHTTPFilter(tt.intentions, tt.intentionDefaultAllow, testLocalInfo, testPeerTrustBundle)
|
filter, err := makeRBACHTTPFilter(tt.intentions, tt.intentionDefaultAllow, testLocalInfo, testPeerTrustBundle, testJWTProviderConfigEntry)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
t.Run("current", func(t *testing.T) {
|
t.Run("current", func(t *testing.T) {
|
||||||
|
@ -1202,7 +1207,7 @@ func TestPathToSegments(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJwtClaimToPrincipal(t *testing.T) {
|
func TestJWTClaimsToPrincipals(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
firstClaim = structs.IntentionJWTClaimVerification{
|
firstClaim = structs.IntentionJWTClaimVerification{
|
||||||
Path: []string{"perms"},
|
Path: []string{"perms"},
|
||||||
|
@ -1234,7 +1239,7 @@ func TestJwtClaimToPrincipal(t *testing.T) {
|
||||||
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
Identifier: &envoy_rbac_v3.Principal_Metadata{
|
||||||
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
Metadata: &envoy_matcher_v3.MetadataMatcher{
|
||||||
Filter: jwtEnvoyFilter,
|
Filter: jwtEnvoyFilter,
|
||||||
Path: pathToSegments(secondClaim.Path, "second-key"),
|
Path: pathToSegments(secondClaim.Path, payloadKey),
|
||||||
Value: &envoy_matcher_v3.ValueMatcher{
|
Value: &envoy_matcher_v3.ValueMatcher{
|
||||||
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
MatchPattern: &envoy_matcher_v3.ValueMatcher_StringMatch{
|
||||||
StringMatch: &envoy_matcher_v3.StringMatcher{
|
StringMatch: &envoy_matcher_v3.StringMatcher{
|
||||||
|
@ -1249,38 +1254,21 @@ func TestJwtClaimToPrincipal(t *testing.T) {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
jwtInfos []*JWTInfo
|
claims []*structs.IntentionJWTClaimVerification
|
||||||
|
metadataPayloadKey string
|
||||||
expected *envoy_rbac_v3.Principal
|
expected *envoy_rbac_v3.Principal
|
||||||
}{
|
}{
|
||||||
"single-jwt-info": {
|
"single-claim": {
|
||||||
jwtInfos: []*JWTInfo{
|
claims: []*structs.IntentionJWTClaimVerification{&firstClaim},
|
||||||
{
|
metadataPayloadKey: payloadKey,
|
||||||
Claims: []*structs.IntentionJWTClaimVerification{&firstClaim},
|
expected: &firstPrincipal,
|
||||||
MetadataPayloadKey: payloadKey,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
"multiple-claims": {
|
||||||
|
claims: []*structs.IntentionJWTClaimVerification{&firstClaim, &secondClaim},
|
||||||
|
metadataPayloadKey: payloadKey,
|
||||||
expected: &envoy_rbac_v3.Principal{
|
expected: &envoy_rbac_v3.Principal{
|
||||||
Identifier: &envoy_rbac_v3.Principal_OrIds{
|
Identifier: &envoy_rbac_v3.Principal_AndIds{
|
||||||
OrIds: &envoy_rbac_v3.Principal_Set{
|
AndIds: &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},
|
Ids: []*envoy_rbac_v3.Principal{&firstPrincipal, &secondPrincipal},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1291,7 +1279,7 @@ func TestJwtClaimToPrincipal(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) {
|
||||||
principal := jwtInfosToPrincipals(tt.jwtInfos)
|
principal := jwtClaimsToPrincipals(tt.claims, tt.metadataPayloadKey)
|
||||||
require.Equal(t, principal, tt.expected)
|
require.Equal(t, principal, tt.expected)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +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_0": {
|
"okta": {
|
||||||
"issuer": "test-issuer",
|
"issuer": "test-issuer",
|
||||||
"payloadInMetadata": "jwt_payload_okta_0",
|
"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",
|
||||||
|
@ -21,10 +21,15 @@
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"prefix": "some-special-path"
|
"prefix": "/"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"providerName": "okta_0"
|
"requiresAny": {
|
||||||
|
"requirements": [
|
||||||
|
{"providerName": "okta"},
|
||||||
|
{"allowMissingOrFailed": {}}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,7 +17,12 @@
|
||||||
"prefix": "/"
|
"prefix": "/"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"providerName": "okta"
|
"requiresAny": {
|
||||||
|
"requirements": [
|
||||||
|
{"providerName": "okta"},
|
||||||
|
{"allowMissingOrFailed": {}}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,20 +17,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"okta_0": {
|
|
||||||
"issuer": "test-issuer",
|
|
||||||
"payloadInMetadata": "jwt_payload_okta_0",
|
|
||||||
"remoteJwks": {
|
|
||||||
"httpUri": {
|
|
||||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
|
||||||
"cluster": "jwks_cluster_okta",
|
|
||||||
"timeout": "1s"
|
|
||||||
},
|
|
||||||
"asyncFetch": {
|
|
||||||
"fastListener": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"auth0": {
|
"auth0": {
|
||||||
"issuer": "another-issuer",
|
"issuer": "another-issuer",
|
||||||
"payloadInMetadata": "jwt_payload_auth0",
|
"payloadInMetadata": "jwt_payload_auth0",
|
||||||
|
@ -47,28 +33,32 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"prefix": "some-special-path"
|
|
||||||
},
|
|
||||||
"requires": {
|
|
||||||
"providerName": "okta_0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"prefix": "/"
|
"prefix": "/"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"providerName": "okta"
|
"requiresAll": {
|
||||||
|
"requirements": [
|
||||||
|
{
|
||||||
|
"requiresAny": {
|
||||||
|
"requirements": [
|
||||||
|
{"providerName": "okta"},
|
||||||
|
{"allowMissingOrFailed": {}}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"match": {
|
"requiresAny": {
|
||||||
"prefix": "/"
|
"requirements": [
|
||||||
},
|
{"providerName": "auth0"},
|
||||||
"requires": {
|
{"allowMissingOrFailed": {}}
|
||||||
"providerName": "auth0"
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -24,7 +24,12 @@
|
||||||
"prefix": "/"
|
"prefix": "/"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"providerName": "okta"
|
"requiresAny": {
|
||||||
|
"requirements": [
|
||||||
|
{"providerName": "okta"},
|
||||||
|
{"allowMissingOrFailed": {}}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -16,37 +16,21 @@
|
||||||
"fastListener": true
|
"fastListener": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"okta_0": {
|
|
||||||
"issuer": "test-issuer",
|
|
||||||
"payloadInMetadata": "jwt_payload_okta_0",
|
|
||||||
"remoteJwks": {
|
|
||||||
"httpUri": {
|
|
||||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
|
||||||
"cluster": "jwks_cluster_okta",
|
|
||||||
"timeout": "1s"
|
|
||||||
},
|
|
||||||
"asyncFetch": {
|
|
||||||
"fastListener": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"match": {
|
|
||||||
"prefix": "some-special-path"
|
|
||||||
},
|
|
||||||
"requires": {
|
|
||||||
"providerName": "okta_0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"match": {
|
"match": {
|
||||||
"prefix": "/"
|
"prefix": "/"
|
||||||
},
|
},
|
||||||
"requires": {
|
"requires": {
|
||||||
"providerName": "okta"
|
"requiresAny": {
|
||||||
|
"requirements": [
|
||||||
|
{"providerName": "okta"},
|
||||||
|
{"allowMissingOrFailed": {}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,36 +6,38 @@
|
||||||
"policies": {
|
"policies": {
|
||||||
"consul-intentions-layer7-0": {
|
"consul-intentions-layer7-0": {
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
{
|
{
|
||||||
"urlPath": {
|
"urlPath": {
|
||||||
"path": {
|
"path": {
|
||||||
"prefix": "some-path"
|
"prefix": "some-path"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter": "envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_okta"},
|
||||||
|
{"key": "iss"}
|
||||||
],
|
],
|
||||||
"principals": [
|
"value": {
|
||||||
{
|
"stringMatch": {
|
||||||
"andIds": {
|
"exact": "mytest.okta-issuer"
|
||||||
"ids": [
|
|
||||||
{
|
|
||||||
"authenticated": {
|
|
||||||
"principalName": {
|
|
||||||
"safeRegex": {
|
|
||||||
"googleRe2": {},
|
|
||||||
"regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"orIds": {
|
|
||||||
"ids": [
|
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"filter": "envoy.filters.http.jwt_authn",
|
"filter": "envoy.filters.http.jwt_authn",
|
||||||
"path": [
|
"path": [
|
||||||
{"key": "jwt_payload_okta_0"},
|
{"key": "jwt_payload_okta"},
|
||||||
{"key": "roles"}
|
{"key": "roles"}
|
||||||
],
|
],
|
||||||
"value": {
|
"value": {
|
||||||
|
@ -51,6 +53,18 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"principals": [
|
||||||
|
{
|
||||||
|
"authenticated": {
|
||||||
|
"principalName": {
|
||||||
|
"safeRegex": {
|
||||||
|
"googleRe2": {},
|
||||||
|
"regex": "^spiffe://test.consul/ns/default/dc/[^/]+/svc/web$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"orIds": {
|
"andIds": {
|
||||||
"ids": [
|
"ids": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_okta"},
|
||||||
|
{"key": "iss"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "mytest.okta-issuer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"filter":"envoy.filters.http.jwt_authn",
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
"policies": {
|
"policies": {
|
||||||
"consul-intentions-layer7-0": {
|
"consul-intentions-layer7-0": {
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
{
|
{
|
||||||
"urlPath": {
|
"urlPath": {
|
||||||
"path": {
|
"path": {
|
||||||
|
@ -13,6 +16,47 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter": "envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0"},
|
||||||
|
{"key": "iss"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "mytest.auth0-issuer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter": "envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0"},
|
||||||
|
{"key": "perms"},
|
||||||
|
{"key": "role"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
{
|
{
|
||||||
"andRules": {
|
"andRules": {
|
||||||
"rules": [
|
"rules": [
|
||||||
|
@ -34,6 +78,44 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter": "envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0"},
|
||||||
|
{"key": "iss"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "mytest.auth0-issuer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter": "envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0"},
|
||||||
|
{"key": "perms"},
|
||||||
|
{"key": "role"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"principals": [
|
"principals": [
|
||||||
|
@ -53,8 +135,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"orIds": {
|
"andIds": {
|
||||||
"ids": [
|
"ids": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_okta"},
|
||||||
|
{"key": "iss"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "mytest.okta-issuer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"filter":"envoy.filters.http.jwt_authn",
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
@ -68,36 +164,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
"policies": {
|
"policies": {
|
||||||
"consul-intentions-layer7-0": {
|
"consul-intentions-layer7-0": {
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
{
|
{
|
||||||
"urlPath": {
|
"urlPath": {
|
||||||
"path": {
|
"path": {
|
||||||
|
@ -13,6 +16,44 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"andRules": {
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter": "envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0"},
|
||||||
|
{"key": "iss"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "mytest.auth0-issuer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter": "envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_auth0"},
|
||||||
|
{"key": "perms"},
|
||||||
|
{"key": "role"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"andRules": {
|
"andRules": {
|
||||||
"rules": [
|
"rules": [
|
||||||
|
@ -53,8 +94,22 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"orIds": {
|
"andIds": {
|
||||||
"ids": [
|
"ids": [
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
"path": [
|
||||||
|
{"key": "jwt_payload_okta"},
|
||||||
|
{"key": "iss"}
|
||||||
|
],
|
||||||
|
"value": {
|
||||||
|
"stringMatch": {
|
||||||
|
"exact": "mytest.okta-issuer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"filter":"envoy.filters.http.jwt_authn",
|
"filter":"envoy.filters.http.jwt_authn",
|
||||||
|
@ -68,21 +123,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"metadata": {
|
|
||||||
"filter":"envoy.filters.http.jwt_authn",
|
|
||||||
"path": [
|
|
||||||
{"key": "jwt_payload_auth0_0"},
|
|
||||||
{"key": "perms"},
|
|
||||||
{"key": "role"}
|
|
||||||
],
|
|
||||||
"value": {
|
|
||||||
"stringMatch": {
|
|
||||||
"exact": "admin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,8 @@ func TestJWTAuthConnectService(t *testing.T) {
|
||||||
configureIntentions(t, cluster)
|
configureIntentions(t, cluster)
|
||||||
|
|
||||||
baseURL := fmt.Sprintf("http://localhost:%d", clientPort)
|
baseURL := fmt.Sprintf("http://localhost:%d", clientPort)
|
||||||
// fails without jwt headers
|
// TODO(roncodingenthusiast): update test to reflect jwt-auth filter in metadata mode
|
||||||
doRequest(t, baseURL, http.StatusUnauthorized, "")
|
doRequest(t, baseURL, http.StatusOK, "")
|
||||||
// succeeds with jwt
|
// succeeds with jwt
|
||||||
doRequest(t, baseURL, http.StatusOK, jwt)
|
doRequest(t, baseURL, http.StatusOK, jwt)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue