Skip to content

Commit 5a95a04

Browse files
authored
fix: missing jwt provider when jwt is configured on multiple listeners sharing the same port (#7337)
* fix jwt provider missing when jwt is configured at multiple ir listeners Signed-off-by: Huabing Zhao <[email protected]>
1 parent 669abd1 commit 5a95a04

File tree

7 files changed

+481
-26
lines changed

7 files changed

+481
-26
lines changed

internal/xds/translator/jwt.go

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,31 +53,39 @@ func (*jwt) patchHCM(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListen
5353
return nil
5454
}
5555

56-
// Return early if filter already exists.
57-
for _, httpFilter := range mgr.HttpFilters {
56+
var jwtAuthn jwtauthnv3.JwtAuthentication
57+
jwtAuthnFilterIndex := -1
58+
// Add providers to existing JwtAuthentication if present.
59+
for index, httpFilter := range mgr.HttpFilters {
5860
if httpFilter.Name == egv1a1.EnvoyFilterJWTAuthn.String() {
59-
return nil
61+
jwtAuthnFilterIndex = index
62+
if err := httpFilter.GetTypedConfig().UnmarshalTo(&jwtAuthn); err != nil {
63+
return err
64+
}
6065
}
6166
}
6267

63-
jwtFilter, err := buildHCMJWTFilter(irListener)
68+
if err := buildJWTAuthn(irListener, &jwtAuthn); err != nil {
69+
return err
70+
}
71+
72+
jwtFilter, err := buildHCMJWTFilter(&jwtAuthn)
6473
if err != nil {
6574
return err
6675
}
6776

68-
mgr.HttpFilters = append([]*hcmv3.HttpFilter{jwtFilter}, mgr.HttpFilters...)
77+
if exist := jwtAuthnFilterIndex != -1; exist {
78+
mgr.HttpFilters[jwtAuthnFilterIndex] = jwtFilter
79+
} else {
80+
mgr.HttpFilters = append([]*hcmv3.HttpFilter{jwtFilter}, mgr.HttpFilters...)
81+
}
6982

7083
return nil
7184
}
7285

7386
// buildHCMJWTFilter returns a JWT authn HTTP filter from the provided IR listener.
74-
func buildHCMJWTFilter(irListener *ir.HTTPListener) (*hcmv3.HttpFilter, error) {
75-
jwtAuthnProto, err := buildJWTAuthn(irListener)
76-
if err != nil {
77-
return nil, err
78-
}
79-
80-
jwtAuthnAny, err := proto.ToAnyWithValidation(jwtAuthnProto)
87+
func buildHCMJWTFilter(jwtAuthn *jwtauthnv3.JwtAuthentication) (*hcmv3.HttpFilter, error) {
88+
jwtAuthnAny, err := proto.ToAnyWithValidation(jwtAuthn)
8189
if err != nil {
8290
return nil, err
8391
}
@@ -91,15 +99,19 @@ func buildHCMJWTFilter(irListener *ir.HTTPListener) (*hcmv3.HttpFilter, error) {
9199
}
92100

93101
// buildJWTAuthn returns a JwtAuthentication based on the provided IR HTTPListener.
94-
func buildJWTAuthn(irListener *ir.HTTPListener) (*jwtauthnv3.JwtAuthentication, error) {
95-
jwtProviders := make(map[string]*jwtauthnv3.JwtProvider)
96-
reqMap := make(map[string]*jwtauthnv3.JwtRequirement)
97-
102+
func buildJWTAuthn(irListener *ir.HTTPListener, jwtAuthn *jwtauthnv3.JwtAuthentication) error {
98103
for _, route := range irListener.Routes {
99104
if route == nil || !routeContainsJWTAuthn(route) {
100105
continue
101106
}
102107

108+
if jwtAuthn.Providers == nil {
109+
jwtAuthn.Providers = make(map[string]*jwtauthnv3.JwtProvider, len(route.Security.JWT.Providers))
110+
}
111+
if jwtAuthn.RequirementMap == nil {
112+
jwtAuthn.RequirementMap = make(map[string]*jwtauthnv3.JwtRequirement, len(route.Security.JWT.Providers))
113+
}
114+
103115
var reqs []*jwtauthnv3.JwtRequirement
104116
for i := range route.Security.JWT.Providers {
105117
var (
@@ -144,7 +156,7 @@ func buildJWTAuthn(irListener *ir.HTTPListener) (*jwtauthnv3.JwtAuthentication,
144156
} else {
145157
var cluster *urlCluster
146158
if cluster, err = url2Cluster(jwks.URI); err != nil {
147-
return nil, err
159+
return err
148160
}
149161
jwksCluster = cluster.name
150162
}
@@ -169,7 +181,7 @@ func buildJWTAuthn(irListener *ir.HTTPListener) (*jwtauthnv3.JwtAuthentication,
169181
if jwks.Traffic != nil && jwks.Traffic.Retry != nil {
170182
var rp *corev3.RetryPolicy
171183
if rp, err = buildNonRouteRetryPolicy(jwks.Traffic.Retry); err != nil {
172-
return nil, err
184+
return err
173185
}
174186
remote.RemoteJwks.RetryPolicy = rp
175187
}
@@ -187,7 +199,7 @@ func buildJWTAuthn(irListener *ir.HTTPListener) (*jwtauthnv3.JwtAuthentication,
187199
}
188200

189201
providerKey := fmt.Sprintf("%s/%s", route.Name, irProvider.Name)
190-
jwtProviders[providerKey] = jwtProvider
202+
jwtAuthn.Providers[providerKey] = jwtProvider
191203
reqs = append(reqs, &jwtauthnv3.JwtRequirement{
192204
RequiresType: &jwtauthnv3.JwtRequirement_ProviderName{
193205
ProviderName: providerKey,
@@ -204,7 +216,7 @@ func buildJWTAuthn(irListener *ir.HTTPListener) (*jwtauthnv3.JwtAuthentication,
204216
}
205217

206218
if len(reqs) == 1 {
207-
reqMap[route.Name] = reqs[0]
219+
jwtAuthn.RequirementMap[route.Name] = reqs[0]
208220
} else {
209221
orListReqs := &jwtauthnv3.JwtRequirement{
210222
RequiresType: &jwtauthnv3.JwtRequirement_RequiresAny{
@@ -213,14 +225,10 @@ func buildJWTAuthn(irListener *ir.HTTPListener) (*jwtauthnv3.JwtAuthentication,
213225
},
214226
},
215227
}
216-
reqMap[route.Name] = orListReqs
228+
jwtAuthn.RequirementMap[route.Name] = orListReqs
217229
}
218230
}
219-
220-
return &jwtauthnv3.JwtAuthentication{
221-
RequirementMap: reqMap,
222-
Providers: jwtProviders,
223-
}, nil
231+
return nil
224232
}
225233

226234
// buildXdsUpstreamTLSSocket returns an xDS TransportSocket that uses envoyTrustBundle
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# This file tests JWT configuration from multiple HTTP listeners sharing the same port won't overlap.
2+
http:
3+
- address: 0.0.0.0
4+
externalPort: 80
5+
hostnames:
6+
- domain1.example.com
7+
isHTTP2: false
8+
metadata:
9+
kind: Gateway
10+
name: external-gateway
11+
namespace: envoy-gateway-system
12+
sectionName: domain1-example-com-http
13+
name: envoy-gateway-system/external-gateway/domain1-example-com-http
14+
path:
15+
escapedSlashesAction: UnescapeAndRedirect
16+
mergeSlashes: true
17+
port: 10080
18+
routes:
19+
- destination:
20+
metadata:
21+
kind: HTTPRoute
22+
name: domain1
23+
namespace: ns1
24+
name: httproute/ns1/domain1/rule/0
25+
settings:
26+
- addressType: IP
27+
endpoints:
28+
- host: 7.7.7.7
29+
port: 80
30+
metadata:
31+
kind: Service
32+
name: app1
33+
namespace: ns1
34+
sectionName: "80"
35+
name: httproute/ns1/domain1/rule/0/backend/0
36+
protocol: HTTP
37+
weight: 1
38+
hostname: domain1.example.com
39+
isHTTP2: false
40+
metadata:
41+
kind: HTTPRoute
42+
name: domain1
43+
namespace: ns1
44+
name: httproute/ns1/domain1/rule/0/match/0/domain1_example_com
45+
pathMatch:
46+
distinct: false
47+
name: ""
48+
prefix: /
49+
security:
50+
jwt:
51+
allowMissing: true
52+
providers:
53+
- extractFrom:
54+
cookies:
55+
- AccessTokenDomain1
56+
issuer: https://accounts.google.com
57+
name: jwt1
58+
remoteJWKS:
59+
uri: https://www.googleapis.com/oauth2/v3/certs
60+
- address: 0.0.0.0
61+
externalPort: 80
62+
hostnames:
63+
- domain2.example.com
64+
isHTTP2: false
65+
metadata:
66+
kind: Gateway
67+
name: external-gateway
68+
namespace: envoy-gateway-system
69+
sectionName: domain2-example-com-http
70+
name: envoy-gateway-system/external-gateway/domain2-example-com-http
71+
path:
72+
escapedSlashesAction: UnescapeAndRedirect
73+
mergeSlashes: true
74+
port: 10080
75+
routes:
76+
- destination:
77+
metadata:
78+
kind: HTTPRoute
79+
name: domain2
80+
namespace: ns2
81+
name: httproute/ns2/domain2/rule/0
82+
settings:
83+
- addressType: IP
84+
endpoints:
85+
- host: 9.9.9.9
86+
port: 80
87+
metadata:
88+
kind: Service
89+
name: app2
90+
namespace: ns2
91+
sectionName: "80"
92+
name: httproute/ns2/domain2/rule/0/backend/0
93+
protocol: HTTP
94+
weight: 1
95+
hostname: domain2.example.com
96+
isHTTP2: false
97+
metadata:
98+
kind: HTTPRoute
99+
name: domain2
100+
namespace: ns2
101+
name: httproute/ns2/domain2/rule/0/match/0/domain2_example_com
102+
pathMatch:
103+
distinct: false
104+
name: ""
105+
prefix: /
106+
security:
107+
jwt:
108+
allowMissing: true
109+
providers:
110+
- extractFrom:
111+
cookies:
112+
- AccessTokenDomain2
113+
issuer: https://accounts.google.com
114+
name: jwt2
115+
remoteJWKS:
116+
uri: https://www.googleapis.com/oauth2/v3/certs
117+
readyListener:
118+
address: 0.0.0.0
119+
ipFamily: IPv4
120+
path: /ready
121+
port: 19003
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
- circuitBreakers:
2+
thresholds:
3+
- maxRetries: 1024
4+
commonLbConfig: {}
5+
connectTimeout: 10s
6+
dnsLookupFamily: V4_PREFERRED
7+
edsClusterConfig:
8+
edsConfig:
9+
ads: {}
10+
resourceApiVersion: V3
11+
serviceName: httproute/ns1/domain1/rule/0
12+
ignoreHealthOnHostRemoval: true
13+
lbPolicy: LEAST_REQUEST
14+
loadBalancingPolicy:
15+
policies:
16+
- typedExtensionConfig:
17+
name: envoy.load_balancing_policies.least_request
18+
typedConfig:
19+
'@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest
20+
localityLbConfig:
21+
localityWeightedLbConfig: {}
22+
metadata:
23+
filterMetadata:
24+
envoy-gateway:
25+
resources:
26+
- kind: HTTPRoute
27+
name: domain1
28+
namespace: ns1
29+
name: httproute/ns1/domain1/rule/0
30+
perConnectionBufferLimitBytes: 32768
31+
type: EDS
32+
- circuitBreakers:
33+
thresholds:
34+
- maxRetries: 1024
35+
commonLbConfig: {}
36+
connectTimeout: 10s
37+
dnsLookupFamily: V4_PREFERRED
38+
dnsRefreshRate: 30s
39+
ignoreHealthOnHostRemoval: true
40+
lbPolicy: LEAST_REQUEST
41+
loadAssignment:
42+
clusterName: www_googleapis_com_443
43+
endpoints:
44+
- lbEndpoints:
45+
- endpoint:
46+
address:
47+
socketAddress:
48+
address: www.googleapis.com
49+
portValue: 443
50+
loadBalancingWeight: 1
51+
loadBalancingWeight: 1
52+
locality:
53+
region: www_googleapis_com_443/backend/-1
54+
loadBalancingPolicy:
55+
policies:
56+
- typedExtensionConfig:
57+
name: envoy.load_balancing_policies.least_request
58+
typedConfig:
59+
'@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest
60+
localityLbConfig:
61+
localityWeightedLbConfig: {}
62+
name: www_googleapis_com_443
63+
perConnectionBufferLimitBytes: 32768
64+
respectDnsTtl: true
65+
transportSocket:
66+
name: envoy.transport_sockets.tls
67+
typedConfig:
68+
'@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
69+
commonTlsContext:
70+
validationContext:
71+
trustedCa:
72+
filename: /etc/ssl/certs/ca-certificates.crt
73+
sni: www.googleapis.com
74+
type: STRICT_DNS
75+
- circuitBreakers:
76+
thresholds:
77+
- maxRetries: 1024
78+
commonLbConfig: {}
79+
connectTimeout: 10s
80+
dnsLookupFamily: V4_PREFERRED
81+
edsClusterConfig:
82+
edsConfig:
83+
ads: {}
84+
resourceApiVersion: V3
85+
serviceName: httproute/ns2/domain2/rule/0
86+
ignoreHealthOnHostRemoval: true
87+
lbPolicy: LEAST_REQUEST
88+
loadBalancingPolicy:
89+
policies:
90+
- typedExtensionConfig:
91+
name: envoy.load_balancing_policies.least_request
92+
typedConfig:
93+
'@type': type.googleapis.com/envoy.extensions.load_balancing_policies.least_request.v3.LeastRequest
94+
localityLbConfig:
95+
localityWeightedLbConfig: {}
96+
metadata:
97+
filterMetadata:
98+
envoy-gateway:
99+
resources:
100+
- kind: HTTPRoute
101+
name: domain2
102+
namespace: ns2
103+
name: httproute/ns2/domain2/rule/0
104+
perConnectionBufferLimitBytes: 32768
105+
type: EDS
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
- clusterName: httproute/ns1/domain1/rule/0
2+
endpoints:
3+
- lbEndpoints:
4+
- endpoint:
5+
address:
6+
socketAddress:
7+
address: 7.7.7.7
8+
portValue: 80
9+
loadBalancingWeight: 1
10+
loadBalancingWeight: 1
11+
locality:
12+
region: httproute/ns1/domain1/rule/0/backend/0
13+
metadata:
14+
filterMetadata:
15+
envoy-gateway:
16+
resources:
17+
- kind: Service
18+
name: app1
19+
namespace: ns1
20+
sectionName: "80"
21+
- clusterName: httproute/ns2/domain2/rule/0
22+
endpoints:
23+
- lbEndpoints:
24+
- endpoint:
25+
address:
26+
socketAddress:
27+
address: 9.9.9.9
28+
portValue: 80
29+
loadBalancingWeight: 1
30+
loadBalancingWeight: 1
31+
locality:
32+
region: httproute/ns2/domain2/rule/0/backend/0
33+
metadata:
34+
filterMetadata:
35+
envoy-gateway:
36+
resources:
37+
- kind: Service
38+
name: app2
39+
namespace: ns2
40+
sectionName: "80"

0 commit comments

Comments
 (0)