mirror of
https://github.com/status-im/consul.git
synced 2025-01-22 03:29:43 +00:00
[NET-3092] Improve jwt-provider tests (#17430)
* [NET-3092] more tests, prior to verify claims work
This commit is contained in:
parent
d935c7b466
commit
ddb25cec0e
@ -88,6 +88,9 @@ func collectJWTRequirements(i *structs.Intention) []*structs.IntentionJWTProvide
|
||||
func getPermissionsProviders(p []*structs.IntentionPermission) []*structs.IntentionJWTProvider {
|
||||
intentionProviders := []*structs.IntentionJWTProvider{}
|
||||
for _, perm := range p {
|
||||
if perm.JWT == nil {
|
||||
continue
|
||||
}
|
||||
intentionProviders = append(intentionProviders, perm.JWT.Providers...)
|
||||
}
|
||||
|
||||
@ -111,12 +114,8 @@ func buildJWTProviderConfig(p *structs.JWTProviderConfigEntry) (*envoy_http_jwt_
|
||||
return nil, err
|
||||
}
|
||||
envoyCfg.JwksSourceSpecifier = specifier
|
||||
} else if remote := p.JSONWebKeySet.Remote; remote.URI != "" {
|
||||
specifier, err := makeRemoteJWKS(remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
envoyCfg.JwksSourceSpecifier = specifier
|
||||
} else if remote := p.JSONWebKeySet.Remote; remote != nil && remote.URI != "" {
|
||||
envoyCfg.JwksSourceSpecifier = makeRemoteJWKS(remote)
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid jwt provider config; missing JSONWebKeySet for provider: %s", p.Name)
|
||||
}
|
||||
@ -168,7 +167,7 @@ func makeLocalJWKS(l *structs.LocalJWKS, pName string) (*envoy_http_jwt_authn_v3
|
||||
return specifier, nil
|
||||
}
|
||||
|
||||
func makeRemoteJWKS(r *structs.RemoteJWKS) (*envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks, error) {
|
||||
func makeRemoteJWKS(r *structs.RemoteJWKS) *envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks {
|
||||
remote_specifier := envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks{
|
||||
RemoteJwks: &envoy_http_jwt_authn_v3.RemoteJwks{
|
||||
HttpUri: &envoy_core_v3.HttpUri{
|
||||
@ -194,7 +193,7 @@ func makeRemoteJWKS(r *structs.RemoteJWKS) (*envoy_http_jwt_authn_v3.JwtProvider
|
||||
remote_specifier.RemoteJwks.RetryPolicy = p
|
||||
}
|
||||
|
||||
return &remote_specifier, nil
|
||||
return &remote_specifier
|
||||
}
|
||||
|
||||
func buildJWTRetryPolicy(r *structs.JWKSRetryPolicy) *envoy_core_v3.RetryPolicy {
|
||||
@ -231,7 +230,7 @@ func buildRouteRule(provider *structs.IntentionJWTProvider, perm *structs.Intent
|
||||
},
|
||||
}
|
||||
|
||||
if perm != nil {
|
||||
if perm != nil && perm.HTTP != nil {
|
||||
if perm.HTTP.PathPrefix != "" {
|
||||
rule.Match.PathSpecifier = &envoy_route_v3.RouteMatch_Prefix{
|
||||
Prefix: perm.HTTP.PathPrefix,
|
||||
|
@ -4,69 +4,147 @@
|
||||
package xds
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
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"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
type ixnOpts struct {
|
||||
src string
|
||||
action structs.IntentionAction
|
||||
jwt *structs.IntentionJWTRequirement
|
||||
perms *structs.IntentionPermission
|
||||
}
|
||||
|
||||
func makeProvider(name string) structs.IntentionJWTProvider {
|
||||
return structs.IntentionJWTProvider{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func decodeJWKS(t *testing.T, jw string) string {
|
||||
s, err := base64.StdEncoding.DecodeString(jw)
|
||||
require.NoError(t, err)
|
||||
return string(s)
|
||||
}
|
||||
|
||||
var (
|
||||
token = "eyJrZXlzIjogW3sKICAiY3J2IjogIlAtMjU2IiwKICAia2V5X29wcyI6IFsKICAgICJ2ZXJpZnkiCiAgXSwKICAia3R5IjogIkVDIiwKICAieCI6ICJXYzl1WnVQYUI3S2gyRk1jOXd0SmpSZThYRDR5VDJBWU5BQWtyWWJWanV3IiwKICAieSI6ICI2OGhSVEppSk5Pd3RyaDRFb1BYZVZuUnVIN2hpU0RKX2xtYmJqZkRmV3EwIiwKICAiYWxnIjogIkVTMjU2IiwKICAidXNlIjogInNpZyIsCiAgImtpZCI6ICJhYzFlOGY5MGVkZGY2MWM0MjljNjFjYTA1YjRmMmUwNyIKfV19"
|
||||
oktaProvider = makeProvider("okta")
|
||||
auth0Provider = makeProvider("auth0")
|
||||
fakeProvider = makeProvider("fake-provider")
|
||||
oktaIntention = &structs.IntentionJWTRequirement{
|
||||
Providers: []*structs.IntentionJWTProvider{
|
||||
&oktaProvider,
|
||||
},
|
||||
}
|
||||
auth0Intention = &structs.IntentionJWTRequirement{
|
||||
Providers: []*structs.IntentionJWTProvider{
|
||||
&auth0Provider,
|
||||
},
|
||||
}
|
||||
fakeIntention = &structs.IntentionJWTRequirement{
|
||||
Providers: []*structs.IntentionJWTProvider{
|
||||
&fakeProvider,
|
||||
},
|
||||
}
|
||||
multiProviderIntentions = &structs.IntentionJWTRequirement{
|
||||
Providers: []*structs.IntentionJWTProvider{
|
||||
&oktaProvider,
|
||||
&auth0Provider,
|
||||
},
|
||||
}
|
||||
pWithOktaProvider = &structs.IntentionPermission{
|
||||
Action: structs.IntentionActionAllow,
|
||||
HTTP: &structs.IntentionHTTPPermission{
|
||||
PathPrefix: "/some-special-path",
|
||||
},
|
||||
JWT: oktaIntention,
|
||||
}
|
||||
pWithMultiProviders = &structs.IntentionPermission{
|
||||
Action: structs.IntentionActionAllow,
|
||||
HTTP: &structs.IntentionHTTPPermission{
|
||||
PathPrefix: "/some-special-path",
|
||||
},
|
||||
JWT: multiProviderIntentions,
|
||||
}
|
||||
pWithNoJWT = &structs.IntentionPermission{
|
||||
Action: structs.IntentionActionAllow,
|
||||
HTTP: &structs.IntentionHTTPPermission{
|
||||
PathPrefix: "/some-special-path",
|
||||
},
|
||||
}
|
||||
fullRetryPolicy = &structs.JWKSRetryPolicy{
|
||||
RetryPolicyBackOff: &structs.RetryPolicyBackOff{
|
||||
BaseInterval: 0,
|
||||
MaxInterval: 10,
|
||||
},
|
||||
NumRetries: 1,
|
||||
}
|
||||
oktaRemoteJWKS = &structs.RemoteJWKS{
|
||||
RequestTimeoutMs: 1000,
|
||||
FetchAsynchronously: true,
|
||||
URI: "https://example-okta.com/.well-known/jwks.json",
|
||||
}
|
||||
auth0RemoteJWKS = &structs.RemoteJWKS{
|
||||
RequestTimeoutMs: 1000,
|
||||
FetchAsynchronously: true,
|
||||
URI: "https://example-auth0.com/.well-known/jwks.json",
|
||||
}
|
||||
extendedRemoteJWKS = &structs.RemoteJWKS{
|
||||
RequestTimeoutMs: 1000,
|
||||
FetchAsynchronously: true,
|
||||
URI: "https://example-okta.com/.well-known/jwks.json",
|
||||
RetryPolicy: fullRetryPolicy,
|
||||
CacheDuration: 20,
|
||||
}
|
||||
localJWKS = &structs.LocalJWKS{
|
||||
JWKS: token,
|
||||
}
|
||||
localJWKSFilename = &structs.LocalJWKS{
|
||||
Filename: "file.txt",
|
||||
}
|
||||
)
|
||||
|
||||
func makeTestIntention(t *testing.T, opts ixnOpts) *structs.Intention {
|
||||
t.Helper()
|
||||
ixn := structs.TestIntention(t)
|
||||
ixn.SourceName = opts.src
|
||||
|
||||
if opts.jwt != nil {
|
||||
ixn.JWT = opts.jwt
|
||||
}
|
||||
if opts.perms != nil {
|
||||
ixn.Permissions = append(ixn.Permissions, opts.perms)
|
||||
}
|
||||
|
||||
if opts.action != "" {
|
||||
ixn.Action = opts.action
|
||||
}
|
||||
return ixn
|
||||
}
|
||||
|
||||
func TestMakeJWTAUTHFilters(t *testing.T) {
|
||||
type ixnOpts struct {
|
||||
src string
|
||||
action structs.IntentionAction
|
||||
jwt *structs.IntentionJWTRequirement
|
||||
perms *structs.IntentionPermission
|
||||
}
|
||||
|
||||
testIntention := func(t *testing.T, opts ixnOpts) *structs.Intention {
|
||||
t.Helper()
|
||||
ixn := structs.TestIntention(t)
|
||||
ixn.SourceName = opts.src
|
||||
|
||||
if opts.jwt != nil {
|
||||
ixn.JWT = opts.jwt
|
||||
}
|
||||
if opts.perms != nil {
|
||||
ixn.Permissions = append(ixn.Permissions, opts.perms)
|
||||
} else {
|
||||
ixn.Action = opts.action
|
||||
}
|
||||
return ixn
|
||||
}
|
||||
|
||||
simplified := func(ixns ...*structs.Intention) structs.SimplifiedIntentions {
|
||||
return structs.SimplifiedIntentions(ixns)
|
||||
}
|
||||
|
||||
var (
|
||||
oktaProvider = structs.IntentionJWTProvider{
|
||||
Name: "okta",
|
||||
}
|
||||
auth0Provider = structs.IntentionJWTProvider{
|
||||
Name: "auth0",
|
||||
}
|
||||
singleProviderIntention = &structs.IntentionJWTRequirement{
|
||||
Providers: []*structs.IntentionJWTProvider{
|
||||
&oktaProvider,
|
||||
},
|
||||
}
|
||||
multiProviderIntentions = &structs.IntentionJWTRequirement{
|
||||
Providers: []*structs.IntentionJWTProvider{
|
||||
&oktaProvider,
|
||||
&auth0Provider,
|
||||
},
|
||||
}
|
||||
remoteCE = map[string]*structs.JWTProviderConfigEntry{
|
||||
"okta": {
|
||||
Kind: "jwt-provider",
|
||||
Name: "okta",
|
||||
Issuer: "test-issuer",
|
||||
JSONWebKeySet: &structs.JSONWebKeySet{
|
||||
Remote: &structs.RemoteJWKS{
|
||||
FetchAsynchronously: true,
|
||||
URI: "https://example-okta.com/.well-known/jwks.json",
|
||||
},
|
||||
Remote: oktaRemoteJWKS,
|
||||
},
|
||||
},
|
||||
"auth0": {
|
||||
@ -74,10 +152,7 @@ func TestMakeJWTAUTHFilters(t *testing.T) {
|
||||
Name: "auth0",
|
||||
Issuer: "another-issuer",
|
||||
JSONWebKeySet: &structs.JSONWebKeySet{
|
||||
Remote: &structs.RemoteJWKS{
|
||||
FetchAsynchronously: true,
|
||||
URI: "https://example-auth0.com/.well-known/jwks.json",
|
||||
},
|
||||
Remote: auth0RemoteJWKS,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -87,43 +162,35 @@ func TestMakeJWTAUTHFilters(t *testing.T) {
|
||||
Name: "okta",
|
||||
Issuer: "test-issuer",
|
||||
JSONWebKeySet: &structs.JSONWebKeySet{
|
||||
Local: &structs.LocalJWKS{
|
||||
JWKS: "eyJrZXlzIjogW3sKICAiY3J2IjogIlAtMjU2IiwKICAia2V5X29wcyI6IFsKICAgICJ2ZXJpZnkiCiAgXSwKICAia3R5IjogIkVDIiwKICAieCI6ICJXYzl1WnVQYUI3S2gyRk1jOXd0SmpSZThYRDR5VDJBWU5BQWtyWWJWanV3IiwKICAieSI6ICI2OGhSVEppSk5Pd3RyaDRFb1BYZVZuUnVIN2hpU0RKX2xtYmJqZkRmV3EwIiwKICAiYWxnIjogIkVTMjU2IiwKICAidXNlIjogInNpZyIsCiAgImtpZCI6ICJhYzFlOGY5MGVkZGY2MWM0MjljNjFjYTA1YjRmMmUwNyIKfV19",
|
||||
},
|
||||
Local: localJWKS,
|
||||
},
|
||||
},
|
||||
}
|
||||
pWithOneProvider = &structs.IntentionPermission{
|
||||
Action: structs.IntentionActionAllow,
|
||||
HTTP: &structs.IntentionHTTPPermission{
|
||||
PathPrefix: "/some-special-path",
|
||||
},
|
||||
JWT: singleProviderIntention,
|
||||
}
|
||||
)
|
||||
|
||||
// All tests here depend on golden files located under: agent/xds/testdata/jwt_authn/*
|
||||
tests := map[string]struct {
|
||||
intentions structs.SimplifiedIntentions
|
||||
provider map[string]*structs.JWTProviderConfigEntry
|
||||
}{
|
||||
"remote-provider": {
|
||||
intentions: simplified(testIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: singleProviderIntention})),
|
||||
intentions: simplified(makeTestIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: oktaIntention})),
|
||||
provider: remoteCE,
|
||||
},
|
||||
"local-provider": {
|
||||
intentions: simplified(testIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: singleProviderIntention})),
|
||||
intentions: simplified(makeTestIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: oktaIntention})),
|
||||
provider: localCE,
|
||||
},
|
||||
"intention-with-path": {
|
||||
intentions: simplified(testIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, perms: pWithOneProvider})),
|
||||
intentions: simplified(makeTestIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, perms: pWithOktaProvider})),
|
||||
provider: remoteCE,
|
||||
},
|
||||
"top-level-provider-with-permission": {
|
||||
intentions: simplified(testIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: singleProviderIntention, perms: pWithOneProvider})),
|
||||
intentions: simplified(makeTestIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: oktaIntention, perms: pWithOktaProvider})),
|
||||
provider: remoteCE,
|
||||
},
|
||||
"multiple-providers-and-one-permission": {
|
||||
intentions: simplified(testIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: multiProviderIntentions, perms: pWithOneProvider})),
|
||||
intentions: simplified(makeTestIntention(t, ixnOpts{src: "web", action: structs.IntentionActionAllow, jwt: multiProviderIntentions, perms: pWithOktaProvider})),
|
||||
provider: remoteCE,
|
||||
},
|
||||
}
|
||||
@ -131,16 +198,476 @@ func TestMakeJWTAUTHFilters(t *testing.T) {
|
||||
for name, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("jwt filter", func(t *testing.T) {
|
||||
filter, err := makeJWTAuthFilter(tt.provider, tt.intentions)
|
||||
require.NoError(t, err)
|
||||
filter, err := makeJWTAuthFilter(tt.provider, tt.intentions)
|
||||
require.NoError(t, err)
|
||||
gotJSON := protoToJSON(t, filter)
|
||||
require.JSONEq(t, goldenSimple(t, filepath.Join("jwt_authn", name), gotJSON), gotJSON)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("current", func(t *testing.T) {
|
||||
gotJSON := protoToJSON(t, filter)
|
||||
func TestCollectJWTRequirements(t *testing.T) {
|
||||
var (
|
||||
emptyReq = []*structs.IntentionJWTProvider{}
|
||||
oneReq = []*structs.IntentionJWTProvider{&oktaProvider}
|
||||
multiReq = append(oneReq, &auth0Provider)
|
||||
)
|
||||
|
||||
require.JSONEq(t, goldenSimple(t, filepath.Join("jwt_authn", name), gotJSON), gotJSON)
|
||||
})
|
||||
tests := map[string]struct {
|
||||
intention *structs.Intention
|
||||
expected []*structs.IntentionJWTProvider
|
||||
}{
|
||||
"empty-top-level-jwt-and-empty-permissions": {
|
||||
intention: makeTestIntention(t, ixnOpts{src: "web"}),
|
||||
expected: emptyReq,
|
||||
},
|
||||
"top-level-jwt-and-empty-permissions": {
|
||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: oktaIntention}),
|
||||
expected: oneReq,
|
||||
},
|
||||
"multi-top-level-jwt-and-empty-permissions": {
|
||||
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: multiProviderIntentions}),
|
||||
expected: multiReq,
|
||||
},
|
||||
"top-level-jwt-and-one-jwt-permission": {
|
||||
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 {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
reqs := collectJWTRequirements(tt.intention)
|
||||
require.ElementsMatch(t, reqs, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPermissionsProviders(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
perms []*structs.IntentionPermission
|
||||
expected []*structs.IntentionJWTProvider
|
||||
}{
|
||||
"empty-permissions": {
|
||||
perms: []*structs.IntentionPermission{},
|
||||
expected: []*structs.IntentionJWTProvider{},
|
||||
},
|
||||
"nil-permissions": {
|
||||
perms: nil,
|
||||
expected: []*structs.IntentionJWTProvider{},
|
||||
},
|
||||
"permissions-with-no-jwt": {
|
||||
perms: []*structs.IntentionPermission{pWithNoJWT},
|
||||
expected: []*structs.IntentionJWTProvider{},
|
||||
},
|
||||
"permissions-with-one-jwt": {
|
||||
perms: []*structs.IntentionPermission{pWithOktaProvider, pWithNoJWT},
|
||||
expected: []*structs.IntentionJWTProvider{&oktaProvider},
|
||||
},
|
||||
"permissions-with-multiple-jwt": {
|
||||
perms: []*structs.IntentionPermission{pWithMultiProviders, pWithNoJWT},
|
||||
expected: []*structs.IntentionJWTProvider{&oktaProvider, &auth0Provider},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("getPermissionsProviders", func(t *testing.T) {
|
||||
p := getPermissionsProviders(tt.perms)
|
||||
|
||||
require.ElementsMatch(t, p, tt.expected)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildJWTProviderConfig(t *testing.T) {
|
||||
var (
|
||||
fullCE = structs.JWTProviderConfigEntry{
|
||||
Kind: "jwt-provider",
|
||||
Issuer: "auth0",
|
||||
Audiences: []string{"aud"},
|
||||
JSONWebKeySet: &structs.JSONWebKeySet{
|
||||
Local: &structs.LocalJWKS{
|
||||
JWKS: token,
|
||||
},
|
||||
},
|
||||
Forwarding: &structs.JWTForwardingConfig{HeaderName: "user-token"},
|
||||
Locations: []*structs.JWTLocation{
|
||||
{Header: &structs.JWTLocationHeader{Forward: true, Name: "Authorization", ValuePrefix: "Bearer"}},
|
||||
},
|
||||
}
|
||||
ceRemoteJWKS = structs.JWTProviderConfigEntry{
|
||||
Kind: "jwt-provider",
|
||||
Issuer: "auth0",
|
||||
Audiences: []string{"aud"},
|
||||
JSONWebKeySet: &structs.JSONWebKeySet{
|
||||
Remote: oktaRemoteJWKS,
|
||||
},
|
||||
}
|
||||
ceInvalidJWKS = structs.JWTProviderConfigEntry{JSONWebKeySet: &structs.JSONWebKeySet{}}
|
||||
)
|
||||
|
||||
tests := map[string]struct {
|
||||
ce *structs.JWTProviderConfigEntry
|
||||
expected *envoy_http_jwt_authn_v3.JwtProvider
|
||||
expectedError string
|
||||
providerName string
|
||||
}{
|
||||
"config-entry-with-invalid-localJWKS": {
|
||||
ce: &ceInvalidJWKS,
|
||||
expectedError: "invalid jwt provider config; missing JSONWebKeySet for provider",
|
||||
},
|
||||
"valid-config-entry": {
|
||||
ce: &fullCE,
|
||||
expected: &envoy_http_jwt_authn_v3.JwtProvider{
|
||||
Issuer: fullCE.Issuer,
|
||||
Audiences: fullCE.Audiences,
|
||||
ForwardPayloadHeader: "user-token",
|
||||
PadForwardPayloadHeader: false,
|
||||
Forward: true,
|
||||
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_LocalJwks{
|
||||
LocalJwks: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: decodeJWKS(t, localJWKS.JWKS),
|
||||
},
|
||||
},
|
||||
},
|
||||
FromHeaders: []*envoy_http_jwt_authn_v3.JwtHeader{{Name: "Authorization", ValuePrefix: "Bearer"}},
|
||||
},
|
||||
},
|
||||
"entry-with-remote-jwks": {
|
||||
ce: &ceRemoteJWKS,
|
||||
expected: &envoy_http_jwt_authn_v3.JwtProvider{
|
||||
Issuer: fullCE.Issuer,
|
||||
Audiences: fullCE.Audiences,
|
||||
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks{
|
||||
RemoteJwks: &envoy_http_jwt_authn_v3.RemoteJwks{
|
||||
HttpUri: &envoy_core_v3.HttpUri{
|
||||
Uri: oktaRemoteJWKS.URI,
|
||||
HttpUpstreamType: &envoy_core_v3.HttpUri_Cluster{Cluster: "jwks_cluster"},
|
||||
Timeout: &durationpb.Duration{Seconds: 1},
|
||||
},
|
||||
AsyncFetch: &envoy_http_jwt_authn_v3.JwksAsyncFetch{
|
||||
FastListener: oktaRemoteJWKS.FetchAsynchronously,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
res, err := buildJWTProviderConfig(tt.ce)
|
||||
|
||||
if tt.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.expectedError)
|
||||
} else {
|
||||
require.Equal(t, res, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestMakeLocalJWKS(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
jwks *structs.LocalJWKS
|
||||
providerName string
|
||||
expected *envoy_http_jwt_authn_v3.JwtProvider_LocalJwks
|
||||
expectedError string
|
||||
}{
|
||||
"invalid-base64-jwks": {
|
||||
jwks: &structs.LocalJWKS{JWKS: "decoded-jwks"},
|
||||
expectedError: "illegal base64 data",
|
||||
},
|
||||
"no-jwks-and-no-filename": {
|
||||
jwks: &structs.LocalJWKS{},
|
||||
expectedError: "invalid jwt provider config; missing JWKS/Filename for local provider",
|
||||
},
|
||||
"localjwks-with-filename": {
|
||||
jwks: localJWKSFilename,
|
||||
expected: &envoy_http_jwt_authn_v3.JwtProvider_LocalJwks{
|
||||
LocalJwks: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_Filename{
|
||||
Filename: localJWKSFilename.Filename,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"localjwks-with-jwks": {
|
||||
jwks: localJWKS,
|
||||
expected: &envoy_http_jwt_authn_v3.JwtProvider_LocalJwks{
|
||||
LocalJwks: &envoy_core_v3.DataSource{
|
||||
Specifier: &envoy_core_v3.DataSource_InlineString{
|
||||
InlineString: decodeJWKS(t, localJWKS.JWKS),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
res, err := makeLocalJWKS(tt.jwks, tt.providerName)
|
||||
|
||||
if tt.expectedError != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.expectedError)
|
||||
} else {
|
||||
require.Equal(t, res, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakeRemoteJWKS(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
jwks *structs.RemoteJWKS
|
||||
expected *envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks
|
||||
}{
|
||||
"with-no-cache-duration": {
|
||||
jwks: oktaRemoteJWKS,
|
||||
expected: &envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks{
|
||||
RemoteJwks: &envoy_http_jwt_authn_v3.RemoteJwks{
|
||||
HttpUri: &envoy_core_v3.HttpUri{
|
||||
Uri: oktaRemoteJWKS.URI,
|
||||
HttpUpstreamType: &envoy_core_v3.HttpUri_Cluster{Cluster: "jwks_cluster"},
|
||||
Timeout: &durationpb.Duration{Seconds: 1},
|
||||
},
|
||||
AsyncFetch: &envoy_http_jwt_authn_v3.JwksAsyncFetch{
|
||||
FastListener: oktaRemoteJWKS.FetchAsynchronously,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"with-retry-policy": {
|
||||
jwks: extendedRemoteJWKS,
|
||||
expected: &envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks{
|
||||
RemoteJwks: &envoy_http_jwt_authn_v3.RemoteJwks{
|
||||
HttpUri: &envoy_core_v3.HttpUri{
|
||||
Uri: oktaRemoteJWKS.URI,
|
||||
HttpUpstreamType: &envoy_core_v3.HttpUri_Cluster{Cluster: "jwks_cluster"},
|
||||
Timeout: &durationpb.Duration{Seconds: 1},
|
||||
},
|
||||
AsyncFetch: &envoy_http_jwt_authn_v3.JwksAsyncFetch{
|
||||
FastListener: oktaRemoteJWKS.FetchAsynchronously,
|
||||
},
|
||||
RetryPolicy: buildJWTRetryPolicy(extendedRemoteJWKS.RetryPolicy),
|
||||
CacheDuration: &durationpb.Duration{Seconds: int64(extendedRemoteJWKS.CacheDuration)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
res := makeRemoteJWKS(tt.jwks)
|
||||
require.Equal(t, res, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildJWTRetryPolicy(t *testing.T) {
|
||||
var (
|
||||
noBackofRetryPolicy = &structs.JWKSRetryPolicy{NumRetries: 1}
|
||||
noNumRetriesPolicy = &structs.JWKSRetryPolicy{
|
||||
RetryPolicyBackOff: &structs.RetryPolicyBackOff{BaseInterval: 0, MaxInterval: 10},
|
||||
}
|
||||
)
|
||||
tests := map[string]struct {
|
||||
retryPolicy *structs.JWKSRetryPolicy
|
||||
expected *envoy_core_v3.RetryPolicy
|
||||
}{
|
||||
"nil-retry-policy": {
|
||||
retryPolicy: nil,
|
||||
expected: nil,
|
||||
},
|
||||
"retry-policy-with-no-backoff": {
|
||||
retryPolicy: noBackofRetryPolicy,
|
||||
expected: &envoy_core_v3.RetryPolicy{NumRetries: wrapperspb.UInt32(uint32(1))},
|
||||
},
|
||||
"retry-policy-with-backoff": {
|
||||
retryPolicy: fullRetryPolicy,
|
||||
expected: &envoy_core_v3.RetryPolicy{
|
||||
NumRetries: wrapperspb.UInt32(uint32(1)),
|
||||
RetryBackOff: &envoy_core_v3.BackoffStrategy{
|
||||
BaseInterval: structs.DurationToProto(0),
|
||||
MaxInterval: structs.DurationToProto(10),
|
||||
},
|
||||
},
|
||||
},
|
||||
"retry-policy-with-no-retries": {
|
||||
retryPolicy: noNumRetriesPolicy,
|
||||
expected: &envoy_core_v3.RetryPolicy{
|
||||
RetryBackOff: &envoy_core_v3.BackoffStrategy{
|
||||
BaseInterval: structs.DurationToProto(0),
|
||||
MaxInterval: structs.DurationToProto(10),
|
||||
},
|
||||
NumRetries: wrapperspb.UInt32(uint32(0)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
res := buildJWTRetryPolicy(tt.retryPolicy)
|
||||
|
||||
require.Equal(t, res, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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: oktaProvider.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"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: oktaProvider.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"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: oktaProvider.Name,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
res := buildRouteRule(tt.provider, tt.perm, tt.route)
|
||||
require.Equal(t, res, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasJWTconfig(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
perms []*structs.IntentionPermission
|
||||
expected bool
|
||||
}{
|
||||
"empty-permissions": {
|
||||
perms: []*structs.IntentionPermission{},
|
||||
expected: false,
|
||||
},
|
||||
"nil-permissions": {
|
||||
perms: nil,
|
||||
expected: false,
|
||||
},
|
||||
"permissions-with-no-jwt": {
|
||||
perms: []*structs.IntentionPermission{pWithNoJWT},
|
||||
expected: false,
|
||||
},
|
||||
"permissions-with-one-jwt": {
|
||||
perms: []*structs.IntentionPermission{pWithOktaProvider, pWithNoJWT},
|
||||
expected: true,
|
||||
},
|
||||
"permissions-with-multiple-jwt": {
|
||||
perms: []*structs.IntentionPermission{pWithMultiProviders, pWithNoJWT},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(name, func(t *testing.T) {
|
||||
res := hasJWTconfig(tt.perms)
|
||||
require.Equal(t, res, tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
"httpUri": {
|
||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||
"cluster": "jwks_cluster",
|
||||
"timeout": "0s"
|
||||
"timeout": "1s"
|
||||
},
|
||||
"asyncFetch": {
|
||||
"fastListener": true
|
||||
|
@ -9,7 +9,7 @@
|
||||
"httpUri": {
|
||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||
"cluster": "jwks_cluster",
|
||||
"timeout": "0s"
|
||||
"timeout": "1s"
|
||||
},
|
||||
"asyncFetch": {
|
||||
"fastListener": true
|
||||
@ -22,7 +22,7 @@
|
||||
"httpUri": {
|
||||
"uri": "https://example-auth0.com/.well-known/jwks.json",
|
||||
"cluster": "jwks_cluster",
|
||||
"timeout": "0s"
|
||||
"timeout": "1s"
|
||||
},
|
||||
"asyncFetch": {
|
||||
"fastListener": true
|
||||
|
@ -9,7 +9,7 @@
|
||||
"httpUri": {
|
||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||
"cluster": "jwks_cluster",
|
||||
"timeout": "0s"
|
||||
"timeout": "1s"
|
||||
},
|
||||
"asyncFetch": {
|
||||
"fastListener": true
|
||||
|
@ -9,7 +9,7 @@
|
||||
"httpUri": {
|
||||
"uri": "https://example-okta.com/.well-known/jwks.json",
|
||||
"cluster": "jwks_cluster",
|
||||
"timeout": "0s"
|
||||
"timeout": "1s"
|
||||
},
|
||||
"asyncFetch": {
|
||||
"fastListener": true
|
||||
|
Loading…
x
Reference in New Issue
Block a user