Skip to content

Commit e3386b4

Browse files
authoredJun 27, 2024
Fuzzing for OpenAPI/CEL vs Webhook validation (istio#51287)
* Fuzzing for OpenAPI/CEL vs Webhook validation This tests + fuzzes that our CEL validation is equivilent to our webhook. This ensures we are not introducing breaking changes (too much or too little) validation in CEL. * fixes
1 parent cd8829f commit e3386b4

File tree

20 files changed

+1369
-12
lines changed

20 files changed

+1369
-12
lines changed
 

‎bin/update_crds.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ if [ -z "${SHA}" ]; then
4747
fail "Unable to retrieve the commit SHA of istio/api from go.mod file. Not updating the CRD file. Please make sure istio/api exists in the Go module.";
4848
fi
4949

50-
git clone "https://${REPO}" "${API_TMP}" && cd "${API_TMP}"
50+
git clone --filter=tree:0 "https://${REPO}" "${API_TMP}" && cd "${API_TMP}"
5151
git checkout "${SHA}"
5252
if [ ! -f "${API_TMP}/kubernetes/customresourcedefinitions.gen.yaml" ]; then
5353
echo "Generated Custom Resource Definitions file does not exist in the commit SHA ${SHA}. Not updating the CRD file."
5454
exit
5555
fi
5656
rm -f "${ROOTDIR}/manifests/charts/base/crds/crd-all.gen.yaml"
5757
cp "${API_TMP}/kubernetes/customresourcedefinitions.gen.yaml" "${ROOTDIR}/manifests/charts/base/crds/crd-all.gen.yaml"
58+
cp "${API_TMP}"/tests/testdata/* "${ROOTDIR}/pkg/config/validation/testdata/crds"
5859

5960
cd "${ROOTDIR}"
6061

‎go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ require (
143143
github.com/evanphx/json-patch v5.9.0+incompatible // indirect
144144
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
145145
github.com/fatih/camelcase v1.0.0 // indirect
146+
github.com/felixge/httpsnoop v1.0.4 // indirect
146147
github.com/go-errors/errors v1.5.1 // indirect
147148
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
148149
github.com/go-logr/stdr v1.2.2 // indirect
@@ -216,6 +217,7 @@ require (
216217
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
217218
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
218219
github.com/xlab/treeprint v1.2.0 // indirect
220+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 // indirect
219221
go.starlark.net v0.0.0-20231121155337-90ade8b19d09 // indirect
220222
go.uber.org/mock v0.4.0 // indirect
221223
go.uber.org/multierr v1.11.0 // indirect
@@ -228,6 +230,7 @@ require (
228230
gopkg.in/ini.v1 v1.67.0 // indirect
229231
k8s.io/component-base v0.30.1 // indirect
230232
k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect
233+
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.29.0 // indirect
231234
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
232235
sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect
233236
sigs.k8s.io/kustomize/kyaml v0.16.0 // indirect
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
Apache License
2+
Version 2.0, January 2004
3+
http://www.apache.org/licenses/
4+
5+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6+
7+
1. Definitions.
8+
9+
"License" shall mean the terms and conditions for use, reproduction,
10+
and distribution as defined by Sections 1 through 9 of this document.
11+
12+
"Licensor" shall mean the copyright owner or entity authorized by
13+
the copyright owner that is granting the License.
14+
15+
"Legal Entity" shall mean the union of the acting entity and all
16+
other entities that control, are controlled by, or are under common
17+
control with that entity. For the purposes of this definition,
18+
"control" means (i) the power, direct or indirect, to cause the
19+
direction or management of such entity, whether by contract or
20+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
21+
outstanding shares, or (iii) beneficial ownership of such entity.
22+
23+
"You" (or "Your") shall mean an individual or Legal Entity
24+
exercising permissions granted by this License.
25+
26+
"Source" form shall mean the preferred form for making modifications,
27+
including but not limited to software source code, documentation
28+
source, and configuration files.
29+
30+
"Object" form shall mean any form resulting from mechanical
31+
transformation or translation of a Source form, including but
32+
not limited to compiled object code, generated documentation,
33+
and conversions to other media types.
34+
35+
"Work" shall mean the work of authorship, whether in Source or
36+
Object form, made available under the License, as indicated by a
37+
copyright notice that is included in or attached to the work
38+
(an example is provided in the Appendix below).
39+
40+
"Derivative Works" shall mean any work, whether in Source or Object
41+
form, that is based on (or derived from) the Work and for which the
42+
editorial revisions, annotations, elaborations, or other modifications
43+
represent, as a whole, an original work of authorship. For the purposes
44+
of this License, Derivative Works shall not include works that remain
45+
separable from, or merely link (or bind by name) to the interfaces of,
46+
the Work and Derivative Works thereof.
47+
48+
"Contribution" shall mean any work of authorship, including
49+
the original version of the Work and any modifications or additions
50+
to that Work or Derivative Works thereof, that is intentionally
51+
submitted to Licensor for inclusion in the Work by the copyright owner
52+
or by an individual or Legal Entity authorized to submit on behalf of
53+
the copyright owner. For the purposes of this definition, "submitted"
54+
means any form of electronic, verbal, or written communication sent
55+
to the Licensor or its representatives, including but not limited to
56+
communication on electronic mailing lists, source code control systems,
57+
and issue tracking systems that are managed by, or on behalf of, the
58+
Licensor for the purpose of discussing and improving the Work, but
59+
excluding communication that is conspicuously marked or otherwise
60+
designated in writing by the copyright owner as "Not a Contribution."
61+
62+
"Contributor" shall mean Licensor and any individual or Legal Entity
63+
on behalf of whom a Contribution has been received by Licensor and
64+
subsequently incorporated within the Work.
65+
66+
2. Grant of Copyright License. Subject to the terms and conditions of
67+
this License, each Contributor hereby grants to You a perpetual,
68+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69+
copyright license to reproduce, prepare Derivative Works of,
70+
publicly display, publicly perform, sublicense, and distribute the
71+
Work and such Derivative Works in Source or Object form.
72+
73+
3. Grant of Patent License. Subject to the terms and conditions of
74+
this License, each Contributor hereby grants to You a perpetual,
75+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76+
(except as stated in this section) patent license to make, have made,
77+
use, offer to sell, sell, import, and otherwise transfer the Work,
78+
where such license applies only to those patent claims licensable
79+
by such Contributor that are necessarily infringed by their
80+
Contribution(s) alone or by combination of their Contribution(s)
81+
with the Work to which such Contribution(s) was submitted. If You
82+
institute patent litigation against any entity (including a
83+
cross-claim or counterclaim in a lawsuit) alleging that the Work
84+
or a Contribution incorporated within the Work constitutes direct
85+
or contributory patent infringement, then any patent licenses
86+
granted to You under this License for that Work shall terminate
87+
as of the date such litigation is filed.
88+
89+
4. Redistribution. You may reproduce and distribute copies of the
90+
Work or Derivative Works thereof in any medium, with or without
91+
modifications, and in Source or Object form, provided that You
92+
meet the following conditions:
93+
94+
(a) You must give any other recipients of the Work or
95+
Derivative Works a copy of this License; and
96+
97+
(b) You must cause any modified files to carry prominent notices
98+
stating that You changed the files; and
99+
100+
(c) You must retain, in the Source form of any Derivative Works
101+
that You distribute, all copyright, patent, trademark, and
102+
attribution notices from the Source form of the Work,
103+
excluding those notices that do not pertain to any part of
104+
the Derivative Works; and
105+
106+
(d) If the Work includes a "NOTICE" text file as part of its
107+
distribution, then any Derivative Works that You distribute must
108+
include a readable copy of the attribution notices contained
109+
within such NOTICE file, excluding those notices that do not
110+
pertain to any part of the Derivative Works, in at least one
111+
of the following places: within a NOTICE text file distributed
112+
as part of the Derivative Works; within the Source form or
113+
documentation, if provided along with the Derivative Works; or,
114+
within a display generated by the Derivative Works, if and
115+
wherever such third-party notices normally appear. The contents
116+
of the NOTICE file are for informational purposes only and
117+
do not modify the License. You may add Your own attribution
118+
notices within Derivative Works that You distribute, alongside
119+
or as an addendum to the NOTICE text from the Work, provided
120+
that such additional attribution notices cannot be construed
121+
as modifying the License.
122+
123+
You may add Your own copyright statement to Your modifications and
124+
may provide additional or different license terms and conditions
125+
for use, reproduction, or distribution of Your modifications, or
126+
for any such Derivative Works as a whole, provided Your use,
127+
reproduction, and distribution of the Work otherwise complies with
128+
the conditions stated in this License.
129+
130+
5. Submission of Contributions. Unless You explicitly state otherwise,
131+
any Contribution intentionally submitted for inclusion in the Work
132+
by You to the Licensor shall be under the terms and conditions of
133+
this License, without any additional terms or conditions.
134+
Notwithstanding the above, nothing herein shall supersede or modify
135+
the terms of any separate license agreement you may have executed
136+
with Licensor regarding such Contributions.
137+
138+
6. Trademarks. This License does not grant permission to use the trade
139+
names, trademarks, service marks, or product names of the Licensor,
140+
except as required for reasonable and customary use in describing the
141+
origin of the Work and reproducing the content of the NOTICE file.
142+
143+
7. Disclaimer of Warranty. Unless required by applicable law or
144+
agreed to in writing, Licensor provides the Work (and each
145+
Contributor provides its Contributions) on an "AS IS" BASIS,
146+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147+
implied, including, without limitation, any warranties or conditions
148+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149+
PARTICULAR PURPOSE. You are solely responsible for determining the
150+
appropriateness of using or redistributing the Work and assume any
151+
risks associated with Your exercise of permissions under this License.
152+
153+
8. Limitation of Liability. In no event and under no legal theory,
154+
whether in tort (including negligence), contract, or otherwise,
155+
unless required by applicable law (such as deliberate and grossly
156+
negligent acts) or agreed to in writing, shall any Contributor be
157+
liable to You for damages, including any direct, indirect, special,
158+
incidental, or consequential damages of any character arising as a
159+
result of this License or out of the use or inability to use the
160+
Work (including but not limited to damages for loss of goodwill,
161+
work stoppage, computer failure or malfunction, or any and all
162+
other commercial damages or losses), even if such Contributor
163+
has been advised of the possibility of such damages.
164+
165+
9. Accepting Warranty or Additional Liability. While redistributing
166+
the Work or Derivative Works thereof, You may choose to offer,
167+
and charge a fee for, acceptance of support, warranty, indemnity,
168+
or other liability obligations and/or rights consistent with this
169+
License. However, in accepting such obligations, You may act only
170+
on Your own behalf and on Your sole responsibility, not on behalf
171+
of any other Contributor, and only if You agree to indemnify,
172+
defend, and hold each Contributor harmless for any liability
173+
incurred by, or claims asserted against, such Contributor by reason
174+
of your accepting any such warranty or additional liability.
175+
176+
END OF TERMS AND CONDITIONS
177+
178+
APPENDIX: How to apply the Apache License to your work.
179+
180+
To apply the Apache License to your work, attach the following
181+
boilerplate notice, with the fields enclosed by brackets "[]"
182+
replaced with your own identifying information. (Don't include
183+
the brackets!) The text should be enclosed in the appropriate
184+
comment syntax for the file format. We also recommend that a
185+
file or class name and description of purpose be included on the
186+
same "printed page" as the copyright notice for easier
187+
identification within third-party archives.
188+
189+
Copyright [yyyy] [name of copyright owner]
190+
191+
Licensed under the Apache License, Version 2.0 (the "License");
192+
you may not use this file except in compliance with the License.
193+
You may obtain a copy of the License at
194+
195+
http://www.apache.org/licenses/LICENSE-2.0
196+
197+
Unless required by applicable law or agreed to in writing, software
198+
distributed under the License is distributed on an "AS IS" BASIS,
199+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200+
See the License for the specific language governing permissions and
201+
limitations under the License.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
Apache License
2+
Version 2.0, January 2004
3+
http://www.apache.org/licenses/
4+
5+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6+
7+
1. Definitions.
8+
9+
"License" shall mean the terms and conditions for use, reproduction,
10+
and distribution as defined by Sections 1 through 9 of this document.
11+
12+
"Licensor" shall mean the copyright owner or entity authorized by
13+
the copyright owner that is granting the License.
14+
15+
"Legal Entity" shall mean the union of the acting entity and all
16+
other entities that control, are controlled by, or are under common
17+
control with that entity. For the purposes of this definition,
18+
"control" means (i) the power, direct or indirect, to cause the
19+
direction or management of such entity, whether by contract or
20+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
21+
outstanding shares, or (iii) beneficial ownership of such entity.
22+
23+
"You" (or "Your") shall mean an individual or Legal Entity
24+
exercising permissions granted by this License.
25+
26+
"Source" form shall mean the preferred form for making modifications,
27+
including but not limited to software source code, documentation
28+
source, and configuration files.
29+
30+
"Object" form shall mean any form resulting from mechanical
31+
transformation or translation of a Source form, including but
32+
not limited to compiled object code, generated documentation,
33+
and conversions to other media types.
34+
35+
"Work" shall mean the work of authorship, whether in Source or
36+
Object form, made available under the License, as indicated by a
37+
copyright notice that is included in or attached to the work
38+
(an example is provided in the Appendix below).
39+
40+
"Derivative Works" shall mean any work, whether in Source or Object
41+
form, that is based on (or derived from) the Work and for which the
42+
editorial revisions, annotations, elaborations, or other modifications
43+
represent, as a whole, an original work of authorship. For the purposes
44+
of this License, Derivative Works shall not include works that remain
45+
separable from, or merely link (or bind by name) to the interfaces of,
46+
the Work and Derivative Works thereof.
47+
48+
"Contribution" shall mean any work of authorship, including
49+
the original version of the Work and any modifications or additions
50+
to that Work or Derivative Works thereof, that is intentionally
51+
submitted to Licensor for inclusion in the Work by the copyright owner
52+
or by an individual or Legal Entity authorized to submit on behalf of
53+
the copyright owner. For the purposes of this definition, "submitted"
54+
means any form of electronic, verbal, or written communication sent
55+
to the Licensor or its representatives, including but not limited to
56+
communication on electronic mailing lists, source code control systems,
57+
and issue tracking systems that are managed by, or on behalf of, the
58+
Licensor for the purpose of discussing and improving the Work, but
59+
excluding communication that is conspicuously marked or otherwise
60+
designated in writing by the copyright owner as "Not a Contribution."
61+
62+
"Contributor" shall mean Licensor and any individual or Legal Entity
63+
on behalf of whom a Contribution has been received by Licensor and
64+
subsequently incorporated within the Work.
65+
66+
2. Grant of Copyright License. Subject to the terms and conditions of
67+
this License, each Contributor hereby grants to You a perpetual,
68+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69+
copyright license to reproduce, prepare Derivative Works of,
70+
publicly display, publicly perform, sublicense, and distribute the
71+
Work and such Derivative Works in Source or Object form.
72+
73+
3. Grant of Patent License. Subject to the terms and conditions of
74+
this License, each Contributor hereby grants to You a perpetual,
75+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76+
(except as stated in this section) patent license to make, have made,
77+
use, offer to sell, sell, import, and otherwise transfer the Work,
78+
where such license applies only to those patent claims licensable
79+
by such Contributor that are necessarily infringed by their
80+
Contribution(s) alone or by combination of their Contribution(s)
81+
with the Work to which such Contribution(s) was submitted. If You
82+
institute patent litigation against any entity (including a
83+
cross-claim or counterclaim in a lawsuit) alleging that the Work
84+
or a Contribution incorporated within the Work constitutes direct
85+
or contributory patent infringement, then any patent licenses
86+
granted to You under this License for that Work shall terminate
87+
as of the date such litigation is filed.
88+
89+
4. Redistribution. You may reproduce and distribute copies of the
90+
Work or Derivative Works thereof in any medium, with or without
91+
modifications, and in Source or Object form, provided that You
92+
meet the following conditions:
93+
94+
(a) You must give any other recipients of the Work or
95+
Derivative Works a copy of this License; and
96+
97+
(b) You must cause any modified files to carry prominent notices
98+
stating that You changed the files; and
99+
100+
(c) You must retain, in the Source form of any Derivative Works
101+
that You distribute, all copyright, patent, trademark, and
102+
attribution notices from the Source form of the Work,
103+
excluding those notices that do not pertain to any part of
104+
the Derivative Works; and
105+
106+
(d) If the Work includes a "NOTICE" text file as part of its
107+
distribution, then any Derivative Works that You distribute must
108+
include a readable copy of the attribution notices contained
109+
within such NOTICE file, excluding those notices that do not
110+
pertain to any part of the Derivative Works, in at least one
111+
of the following places: within a NOTICE text file distributed
112+
as part of the Derivative Works; within the Source form or
113+
documentation, if provided along with the Derivative Works; or,
114+
within a display generated by the Derivative Works, if and
115+
wherever such third-party notices normally appear. The contents
116+
of the NOTICE file are for informational purposes only and
117+
do not modify the License. You may add Your own attribution
118+
notices within Derivative Works that You distribute, alongside
119+
or as an addendum to the NOTICE text from the Work, provided
120+
that such additional attribution notices cannot be construed
121+
as modifying the License.
122+
123+
You may add Your own copyright statement to Your modifications and
124+
may provide additional or different license terms and conditions
125+
for use, reproduction, or distribution of Your modifications, or
126+
for any such Derivative Works as a whole, provided Your use,
127+
reproduction, and distribution of the Work otherwise complies with
128+
the conditions stated in this License.
129+
130+
5. Submission of Contributions. Unless You explicitly state otherwise,
131+
any Contribution intentionally submitted for inclusion in the Work
132+
by You to the Licensor shall be under the terms and conditions of
133+
this License, without any additional terms or conditions.
134+
Notwithstanding the above, nothing herein shall supersede or modify
135+
the terms of any separate license agreement you may have executed
136+
with Licensor regarding such Contributions.
137+
138+
6. Trademarks. This License does not grant permission to use the trade
139+
names, trademarks, service marks, or product names of the Licensor,
140+
except as required for reasonable and customary use in describing the
141+
origin of the Work and reproducing the content of the NOTICE file.
142+
143+
7. Disclaimer of Warranty. Unless required by applicable law or
144+
agreed to in writing, Licensor provides the Work (and each
145+
Contributor provides its Contributions) on an "AS IS" BASIS,
146+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147+
implied, including, without limitation, any warranties or conditions
148+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149+
PARTICULAR PURPOSE. You are solely responsible for determining the
150+
appropriateness of using or redistributing the Work and assume any
151+
risks associated with Your exercise of permissions under this License.
152+
153+
8. Limitation of Liability. In no event and under no legal theory,
154+
whether in tort (including negligence), contract, or otherwise,
155+
unless required by applicable law (such as deliberate and grossly
156+
negligent acts) or agreed to in writing, shall any Contributor be
157+
liable to You for damages, including any direct, indirect, special,
158+
incidental, or consequential damages of any character arising as a
159+
result of this License or out of the use or inability to use the
160+
Work (including but not limited to damages for loss of goodwill,
161+
work stoppage, computer failure or malfunction, or any and all
162+
other commercial damages or losses), even if such Contributor
163+
has been advised of the possibility of such damages.
164+
165+
9. Accepting Warranty or Additional Liability. While redistributing
166+
the Work or Derivative Works thereof, You may choose to offer,
167+
and charge a fee for, acceptance of support, warranty, indemnity,
168+
or other liability obligations and/or rights consistent with this
169+
License. However, in accepting such obligations, You may act only
170+
on Your own behalf and on Your sole responsibility, not on behalf
171+
of any other Contributor, and only if You agree to indemnify,
172+
defend, and hold each Contributor harmless for any liability
173+
incurred by, or claims asserted against, such Contributor by reason
174+
of your accepting any such warranty or additional liability.
175+
176+
END OF TERMS AND CONDITIONS
177+
178+
APPENDIX: How to apply the Apache License to your work.
179+
180+
To apply the Apache License to your work, attach the following
181+
boilerplate notice, with the fields enclosed by brackets "{}"
182+
replaced with your own identifying information. (Don't include
183+
the brackets!) The text should be enclosed in the appropriate
184+
comment syntax for the file format. We also recommend that a
185+
file or class name and description of purpose be included on the
186+
same "printed page" as the copyright notice for easier
187+
identification within third-party archives.
188+
189+
Copyright {yyyy} {name of copyright owner}
190+
191+
Licensed under the Apache License, Version 2.0 (the "License");
192+
you may not use this file except in compliance with the License.
193+
You may obtain a copy of the License at
194+
195+
http://www.apache.org/licenses/LICENSE-2.0
196+
197+
Unless required by applicable law or agreed to in writing, software
198+
distributed under the License is distributed on an "AS IS" BASIS,
199+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200+
See the License for the specific language governing permissions and
201+
limitations under the License.

‎pilot/pkg/config/kube/crd/conversion.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ func FromJSON(s resource.Schema, js string) (config.Spec, error) {
4444
return c, nil
4545
}
4646

47+
func FromJSONStrict(s resource.Schema, js string) (config.Spec, error) {
48+
c, err := s.NewInstance()
49+
if err != nil {
50+
return nil, err
51+
}
52+
if err = config.ApplyJSONStrict(c, js); err != nil {
53+
return nil, err
54+
}
55+
return c, nil
56+
}
57+
4758
func StatusJSONFromMap(schema resource.Schema, jsonMap *json.RawMessage) (config.Status, error) {
4859
if jsonMap == nil {
4960
return nil, nil
@@ -90,13 +101,14 @@ func FromJSONMap(s resource.Schema, data any) (config.Spec, error) {
90101
return out, nil
91102
}
92103

93-
// ConvertObject converts an IstioObject k8s-style object to the internal configuration model.
94-
func ConvertObject(schema resource.Schema, object IstioObject, domain string) (*config.Config, error) {
104+
type ConversionFunc = func(s resource.Schema, js string) (config.Spec, error)
105+
106+
func ConvertObjectInternal(schema resource.Schema, object IstioObject, domain string, convert ConversionFunc) (*config.Config, error) {
95107
js, err := json.Marshal(object.GetSpec())
96108
if err != nil {
97109
return nil, err
98110
}
99-
spec, err := FromJSON(schema, string(js))
111+
spec, err := convert(schema, string(js))
100112
if err != nil {
101113
return nil, err
102114
}
@@ -122,6 +134,11 @@ func ConvertObject(schema resource.Schema, object IstioObject, domain string) (*
122134
}, nil
123135
}
124136

137+
// ConvertObject converts an IstioObject k8s-style object to the internal configuration model.
138+
func ConvertObject(schema resource.Schema, object IstioObject, domain string) (*config.Config, error) {
139+
return ConvertObjectInternal(schema, object, domain, FromJSON)
140+
}
141+
125142
// ConvertConfig translates Istio config to k8s config JSON
126143
func ConvertConfig(cfg config.Config) (IstioObject, error) {
127144
spec, err := config.ToRaw(cfg.Spec)

‎pilot/pkg/config/kube/gateway/testdata/http.yaml

-3
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ spec:
145145
- path:
146146
type: PathPrefix
147147
value: /prefix-original
148-
name: prefix-path-rewrite
149148
filters:
150149
- type: URLRewrite
151150
urlRewrite:
@@ -160,7 +159,6 @@ spec:
160159
- path:
161160
type: PathPrefix
162161
value: /prefix-to-be-removed
163-
name: prefix-to-be-removed
164162
filters:
165163
- type: URLRewrite
166164
urlRewrite:
@@ -174,7 +172,6 @@ spec:
174172
- path:
175173
type: PathPrefix
176174
value: /full-original
177-
name: full-path-rewrite
178175
filters:
179176
- type: URLRewrite
180177
urlRewrite:

‎pkg/config/crd/validator.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ import (
2828
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
2929
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
3030
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
31+
apiextval "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
3132
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
3233
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
3334
structuraldefaulting "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/defaulting"
35+
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
36+
structuralpruning "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
3437
"k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
3538
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3639
"k8s.io/apimachinery/pkg/runtime"
@@ -39,6 +42,7 @@ import (
3942
celconfig "k8s.io/apiserver/pkg/apis/cel"
4043
"sigs.k8s.io/yaml"
4144

45+
"istio.io/istio/pkg/slices"
4246
"istio.io/istio/pkg/test"
4347
"istio.io/istio/pkg/test/env"
4448
"istio.io/istio/pkg/test/util/yml"
@@ -141,6 +145,19 @@ func (v *Validator) ValidateCustomResource(o runtime.Object) error {
141145
if err := validation.ValidateCustomResource(nil, un.Object, vd).ToAggregate(); err != nil {
142146
return fmt.Errorf("%v/%v/%v: %v", un.GroupVersionKind().Kind, un.GetName(), un.GetNamespace(), err)
143147
}
148+
if err := structurallisttype.ValidateListSetsAndMaps(nil, structural, un.Object).ToAggregate(); err != nil {
149+
return fmt.Errorf("%v/%v/%v: %v", un.GroupVersionKind().Kind, un.GetName(), un.GetNamespace(), err)
150+
}
151+
pruneOpts := structuralschema.UnknownFieldPathOptions{TrackUnknownFieldPaths: true}
152+
unknownFieldPaths := structuralpruning.PruneWithOptions(un.DeepCopy().Object, structural, false, pruneOpts)
153+
unknownFieldPaths = slices.FilterInPlace(unknownFieldPaths, func(s string) bool {
154+
// Some CRDs don't spell out all the fields in metadata, and k8s doesn't care
155+
return !strings.HasPrefix(s, "metadata.")
156+
})
157+
if len(unknownFieldPaths) > 0 {
158+
return fmt.Errorf("%v/%v/%v: unknown fields %v", un.GroupVersionKind().Kind, un.GetName(), un.GetNamespace(), unknownFieldPaths)
159+
}
160+
144161
errs, _ := v.cel[un.GroupVersionKind()].Validate(context.Background(), nil, structural, un.Object, nil, celconfig.RuntimeCELCostBudget)
145162
if errs.ToAggregate() != nil {
146163
return fmt.Errorf("%v/%v/%v: %v", un.GroupVersionKind().Kind, un.GetName(), un.GetNamespace(), errs.ToAggregate().Error())
@@ -221,6 +238,13 @@ func NewValidatorFromCRDs(crds ...apiextensions.CustomResourceDefinition) (*Vali
221238
if len(versions) == 0 {
222239
versions = []apiextensions.CustomResourceDefinitionVersion{{Name: crd.Spec.Version}} // nolint: staticcheck
223240
}
241+
crd.Status.StoredVersions = slices.Map(versions, func(e apiextensions.CustomResourceDefinitionVersion) string {
242+
return e.Name
243+
})
244+
errs := apiextval.ValidateCustomResourceDefinition(context.Background(), &crd)
245+
if len(errs) > 0 {
246+
return nil, fmt.Errorf("CRD %v is not valid: %v", crd.Name, errs.ToAggregate())
247+
}
224248
for _, ver := range versions {
225249
gvk := schema.GroupVersionKind{
226250
Group: crd.Spec.Group,
@@ -260,7 +284,8 @@ func NewValidatorFromCRDs(crds ...apiextensions.CustomResourceDefinition) (*Vali
260284
func NewIstioValidator(t test.Failer) *Validator {
261285
v, err := NewValidatorFromFiles(
262286
filepath.Join(env.IstioSrc, "tests/integration/pilot/testdata/gateway-api-crd.yaml"),
263-
filepath.Join(env.IstioSrc, "manifests/charts/base/crds/crd-all.gen.yaml"))
287+
filepath.Join(env.IstioSrc, "manifests/charts/base/crds/crd-all.gen.yaml"),
288+
)
264289
if err != nil {
265290
t.Fatal(err)
266291
}

‎pkg/config/validation/openapi_test.go

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright Istio Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package validation_test
16+
17+
import (
18+
"fmt"
19+
"os"
20+
"path/filepath"
21+
"regexp"
22+
"strings"
23+
"testing"
24+
25+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
26+
"sigs.k8s.io/yaml"
27+
28+
"istio.io/istio/pilot/pkg/config/kube/crd"
29+
crdvalidation "istio.io/istio/pkg/config/crd"
30+
"istio.io/istio/pkg/config/schema/collections"
31+
"istio.io/istio/pkg/config/schema/resource"
32+
"istio.io/istio/pkg/fuzz"
33+
"istio.io/istio/pkg/test/util/yml"
34+
)
35+
36+
type TestExpectation struct {
37+
WantErr string `json:"_err,omitempty"`
38+
}
39+
40+
func FuzzCRDs(f *testing.F) {
41+
v := crdvalidation.NewIstioValidator(f)
42+
base := "testdata/crds"
43+
d, err := os.ReadDir(base)
44+
if err != nil {
45+
f.Fatal(err)
46+
}
47+
for _, ff := range d {
48+
fc, err := os.ReadFile(filepath.Join(base, ff.Name()))
49+
if err != nil {
50+
f.Fatal(err)
51+
}
52+
f.Add(string(fc))
53+
}
54+
f.Fuzz(func(t *testing.T, data string) {
55+
t.Log("Running", data)
56+
defer fuzz.Finalize()
57+
compareCRDandWebhookValidation(t, true, data, v)
58+
})
59+
}
60+
61+
func TestCRDs(t *testing.T) {
62+
v := crdvalidation.NewIstioValidator(t)
63+
base := "testdata/crds"
64+
d, err := os.ReadDir(base)
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
for _, f := range d {
69+
t.Run(f.Name(), func(t *testing.T) {
70+
f, err := os.ReadFile(filepath.Join(base, f.Name()))
71+
if err != nil {
72+
t.Fatal(err)
73+
}
74+
for _, item := range yml.SplitString(string(f)) {
75+
compareCRDandWebhookValidation(t, false, item, v)
76+
}
77+
})
78+
}
79+
}
80+
81+
func compareCRDandWebhookValidation(t *testing.T, fuzz bool, item string, v *crdvalidation.Validator) {
82+
parseError := t.Fatal
83+
if fuzz {
84+
// Invalid input is skipped for fuzzer
85+
parseError = t.Skip
86+
}
87+
openAPI := validateOpenAPI(parseError, item, v)
88+
name, webhook := validateWebhook(parseError, item)
89+
90+
want := TestExpectation{}
91+
if err := yaml.Unmarshal([]byte(item), &want); err != nil {
92+
parseError(err)
93+
}
94+
95+
t.Run(name, func(t *testing.T) {
96+
if strings.Contains(want.WantErr, "should be at least 1 chars long") {
97+
t.Skip("webhook cannot handle '' vs unset")
98+
}
99+
if openAPI != nil && nilError.MatchString(openAPI.Error()) {
100+
// Kubernetes will reject something like `foo: null`. We cannot distinguish this in the webhook at all.
101+
t.Skip(openAPI)
102+
}
103+
if openAPI != nil && intEnumError.MatchString(openAPI.Error()) {
104+
// CRD only allows string enums. Webhook allows ints, and we cannot really change this.
105+
t.Skip(openAPI)
106+
}
107+
if openAPI != nil && strings.Contains(openAPI.Error(), "unknown fields") {
108+
// We cannot reliably do this in webhook and it not really testing the webhook anyways
109+
// Even strict mode in json processing is not enough as it is case insensitive.
110+
t.Skip(openAPI)
111+
}
112+
if webhook != nil && strings.Contains(webhook.Error(), "jwks parse error") {
113+
// We cannot parse this in CEL yet
114+
t.Skip(webhook)
115+
}
116+
if (openAPI == nil) != (webhook == nil) {
117+
t.Fatalf("mismatch:\ncrd : %v\nwebhook: %v", openAPI, webhook)
118+
}
119+
})
120+
}
121+
122+
func validateWebhook(parseError func(args ...any), item string) (string, error) {
123+
var u map[string]any
124+
if err := yaml.Unmarshal([]byte(item), &u); err != nil {
125+
parseError(err)
126+
}
127+
delete(u, "_err")
128+
by, err := yaml.Marshal(u)
129+
if err != nil {
130+
parseError(err)
131+
}
132+
var obj crd.IstioKind
133+
if err := yaml.UnmarshalStrict(by, &obj); err != nil {
134+
parseError(err)
135+
}
136+
137+
gvk := obj.GroupVersionKind()
138+
s, exists := collections.All.FindByGroupVersionAliasesKind(resource.FromKubernetesGVK(&gvk))
139+
if !exists {
140+
parseError(fmt.Sprintf("unrecognized type: %v", gvk))
141+
}
142+
out, err := crd.ConvertObject(s, &obj, "cluster.local")
143+
if err != nil {
144+
if strings.Contains(err.Error(), "bad Duration") ||
145+
strings.Contains(err.Error(), "unknown value") ||
146+
strings.Contains(err.Error(), "invalid character") {
147+
// These are basically validation errors, but webhook captures them in the 'wrong' place. Treat as non-parse error
148+
return "", err
149+
}
150+
parseError(fmt.Sprintf("convert: %v", err))
151+
}
152+
_, e := s.ValidateConfig(*out)
153+
return obj.Name, e
154+
}
155+
156+
var (
157+
nilError = regexp.MustCompile(`in body must be of type .+?: "null"`)
158+
intEnumError = regexp.MustCompile(`in body must be of type string: "integer", .+?: Unsupported value: .+?: supported values:`)
159+
)
160+
161+
func validateOpenAPI(parseError func(args ...any), item string, v *crdvalidation.Validator) error {
162+
us := &unstructured.Unstructured{}
163+
if err := yaml.Unmarshal([]byte(item), us); err != nil {
164+
parseError(err)
165+
}
166+
delete(us.Object, "_err")
167+
// Parse early so we skip for fuzzing instead of failing
168+
gvk := us.GroupVersionKind()
169+
_, exists := collections.All.FindByGroupVersionAliasesKind(resource.FromKubernetesGVK(&gvk))
170+
if !exists {
171+
parseError(fmt.Sprintf("unrecognized type: %v", gvk))
172+
}
173+
for k := range us.Object {
174+
if k != "spec" && strings.EqualFold(k, "spec") {
175+
parseError(k)
176+
}
177+
}
178+
return v.ValidateCustomResource(us)
179+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
_err: 'Unsupported value: "BLAH"'
2+
apiVersion: security.istio.io/v1
3+
kind: PeerAuthentication
4+
metadata:
5+
name: bad-mode
6+
spec:
7+
mtls:
8+
mode: BLAH
9+
---
10+
_err: type conversion error from
11+
apiVersion: security.istio.io/v1
12+
kind: PeerAuthentication
13+
metadata:
14+
name: bad-port
15+
spec:
16+
selector:
17+
matchLabels:
18+
foo: bar
19+
portLevelMtls:
20+
"acd":
21+
mode: STRICT
22+
---
23+
_err: portLevelMtls requires selector
24+
apiVersion: security.istio.io/v1
25+
kind: PeerAuthentication
26+
metadata:
27+
name: port-level-global
28+
spec:
29+
portLevelMtls:
30+
"80":
31+
mode: STRICT
32+
---
33+
_err: spec.portLevelMtls in body should have at least 1 properties
34+
apiVersion: security.istio.io/v1
35+
kind: PeerAuthentication
36+
metadata:
37+
name: empty-port-level
38+
spec:
39+
selector:
40+
matchLabels:
41+
foo: bar
42+
portLevelMtls: {}
43+
---
44+
_err: port must be between 1-65535
45+
apiVersion: security.istio.io/v1
46+
kind: PeerAuthentication
47+
metadata:
48+
name: zero-port
49+
spec:
50+
selector:
51+
matchLabels:
52+
foo: bar
53+
portLevelMtls:
54+
"0":
55+
mode: STRICT
56+
---
57+
_err: port must be between 1-65535
58+
apiVersion: security.istio.io/v1
59+
kind: PeerAuthentication
60+
metadata:
61+
name: high-port
62+
spec:
63+
selector:
64+
matchLabels:
65+
foo: bar
66+
portLevelMtls:
67+
"42949672":
68+
mode: STRICT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
apiVersion: security.istio.io/v1
2+
kind: PeerAuthentication
3+
metadata:
4+
name: full
5+
spec:
6+
selector:
7+
matchLabels:
8+
foo: bar
9+
mtls:
10+
mode: PERMISSIVE
11+
portLevelMtls:
12+
"80":
13+
mode: STRICT
14+
---
15+
# Weird but valid
16+
apiVersion: security.istio.io/v1
17+
kind: PeerAuthentication
18+
metadata:
19+
name: partial-selector
20+
spec:
21+
selector: {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
_err: spec.concurrency in body should be greater than or equal to 0
2+
apiVersion: networking.istio.io/v1beta1
3+
kind: ProxyConfig
4+
metadata:
5+
name: bad-concurrency
6+
spec:
7+
concurrency: -1
8+
---
9+
_err: key must not be empty
10+
apiVersion: networking.istio.io/v1beta1
11+
kind: ProxyConfig
12+
metadata:
13+
name: empty-key-selector
14+
spec:
15+
selector:
16+
matchLabels:
17+
"": bar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: networking.istio.io/v1beta1
2+
kind: ProxyConfig
3+
metadata:
4+
name: full
5+
spec:
6+
concurrency: 1
7+
selector:
8+
matchLabels:
9+
foo: bar
10+
image:
11+
imageType: foo
12+
environmentVariables:
13+
foo: baz
14+
---
15+
# Silly but valid
16+
apiVersion: networking.istio.io/v1beta1
17+
kind: ProxyConfig
18+
metadata:
19+
name: empty-selector
20+
spec:
21+
selector:
22+
matchLabels: {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# Missing cases
2+
# * Invalid JWKS (not feasible with CEL)
3+
4+
_err: only one of targetRefs or workloadSelector can be set
5+
apiVersion: security.istio.io/v1
6+
kind: RequestAuthentication
7+
metadata:
8+
name: multi-refs
9+
spec:
10+
targetRefs:
11+
- group: ""
12+
kind: Service
13+
name: foo
14+
namespace: bar
15+
targetRef:
16+
group: ""
17+
kind: Service
18+
name: foo
19+
namespace: bar
20+
---
21+
_err: only one of targetRefs or workloadSelector can be set
22+
apiVersion: security.istio.io/v1
23+
kind: RequestAuthentication
24+
metadata:
25+
name: ref-and-selector
26+
spec:
27+
selector:
28+
matchLabels:
29+
a: b
30+
targetRef:
31+
group: ""
32+
kind: Service
33+
name: foo
34+
---
35+
_err: "spec.targetRef.name: Required value"
36+
apiVersion: security.istio.io/v1
37+
kind: RequestAuthentication
38+
metadata:
39+
name: bad-target-name
40+
spec:
41+
targetRef:
42+
group: ""
43+
kind: Service
44+
---
45+
_err: cross namespace referencing is not currently supported
46+
apiVersion: security.istio.io/v1
47+
kind: RequestAuthentication
48+
metadata:
49+
name: bad-target-namespace
50+
spec:
51+
targetRef:
52+
group: ""
53+
kind: Service
54+
name: foo
55+
namespace: bar
56+
---
57+
_err: spec.targetRef.group in body should match
58+
apiVersion: security.istio.io/v1
59+
kind: RequestAuthentication
60+
metadata:
61+
name: bad-target-group
62+
spec:
63+
targetRef:
64+
group: "__"
65+
kind: Service
66+
name: foo
67+
namespace: bar
68+
---
69+
_err: spec.targetRef.kind in body should match
70+
apiVersion: security.istio.io/v1
71+
kind: RequestAuthentication
72+
metadata:
73+
name: bad-target-kind
74+
spec:
75+
targetRef:
76+
group: ""
77+
kind: Serv_ice
78+
name: foo
79+
namespace: bar
80+
---
81+
_err: 'spec.jwtRules[0] in body must be of type object'
82+
apiVersion: security.istio.io/v1
83+
kind: RequestAuthentication
84+
metadata:
85+
name: no-issuer
86+
spec:
87+
jwtRules:
88+
-
89+
---
90+
_err: 'audiences[0] in body should be at least 1 chars long'
91+
apiVersion: security.istio.io/v1
92+
kind: RequestAuthentication
93+
metadata:
94+
name: empty-aud
95+
spec:
96+
jwtRules:
97+
- issuer: example
98+
audiences:
99+
- ""
100+
---
101+
_err: url must have scheme
102+
apiVersion: security.istio.io/v1
103+
kind: RequestAuthentication
104+
metadata:
105+
name: invalid-jwks
106+
spec:
107+
jwtRules:
108+
- issuer: example
109+
jwksUri: "hTPp\\\blah"
110+
---
111+
_err: 'spec.jwtRules[0].fromHeaders[0].name: Required value'
112+
apiVersion: security.istio.io/v1
113+
kind: RequestAuthentication
114+
metadata:
115+
name: invalid-from-headers-no-name
116+
spec:
117+
jwtRules:
118+
- issuer: example
119+
fromHeaders:
120+
- prefix: baz
121+
---
122+
_err: 'spec.jwtRules[0].fromHeaders[0].name in body should be at least 1 chars long'
123+
apiVersion: security.istio.io/v1
124+
kind: RequestAuthentication
125+
metadata:
126+
name: invalid-from-headers-empty-name
127+
spec:
128+
jwtRules:
129+
- issuer: example
130+
fromHeaders:
131+
- name: ""
132+
prefix: baz
133+
---
134+
_err: 'fromParams[0] in body should be at least 1 chars long'
135+
apiVersion: security.istio.io/v1
136+
kind: RequestAuthentication
137+
metadata:
138+
name: invalid-from-params
139+
spec:
140+
jwtRules:
141+
- issuer: example
142+
fromParams:
143+
- ""
144+
---
145+
_err: 'fromCookies[0] in body should be at least 1 chars long'
146+
apiVersion: security.istio.io/v1
147+
kind: RequestAuthentication
148+
metadata:
149+
name: invalid-from-cookies
150+
spec:
151+
jwtRules:
152+
- issuer: example
153+
fromCookies:
154+
- ""
155+
---
156+
_err: 'spec.jwtRules[0].outputClaimToHeaders[0] in body must be of type object'
157+
apiVersion: security.istio.io/v1
158+
kind: RequestAuthentication
159+
metadata:
160+
name: invalid-claim-to-header-unset
161+
spec:
162+
jwtRules:
163+
- issuer: example
164+
outputClaimToHeaders:
165+
- ~
166+
---
167+
_err: 'spec.jwtRules[0].outputClaimToHeaders[0].claim in body should be at least 1 chars long'
168+
apiVersion: security.istio.io/v1
169+
kind: RequestAuthentication
170+
metadata:
171+
name: invalid-claim-to-header-claim-empty
172+
spec:
173+
jwtRules:
174+
- issuer: example
175+
outputClaimToHeaders:
176+
- claim: ""
177+
header: "h"
178+
---
179+
_err: 'header in body should be at least 1 chars long'
180+
apiVersion: security.istio.io/v1
181+
kind: RequestAuthentication
182+
metadata:
183+
name: invalid-claim-to-header-header-empty
184+
spec:
185+
jwtRules:
186+
- issuer: example
187+
outputClaimToHeaders:
188+
- claim: "x"
189+
header: ""
190+
---
191+
_err: 'header in body should match'
192+
apiVersion: security.istio.io/v1
193+
kind: RequestAuthentication
194+
metadata:
195+
name: invalid-claim-to-header-bad-header
196+
spec:
197+
jwtRules:
198+
- issuer: example
199+
outputClaimToHeaders:
200+
- claim: "x"
201+
header: ":authority"
202+
---
203+
_err: must be a valid duration greater than
204+
apiVersion: security.istio.io/v1
205+
kind: RequestAuthentication
206+
metadata:
207+
name: bad-timeout
208+
spec:
209+
jwtRules:
210+
- issuer: example
211+
timeout: "apple"
212+
---
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
apiVersion: security.istio.io/v1
2+
kind: RequestAuthentication
3+
metadata:
4+
name: full
5+
spec:
6+
targetRefs:
7+
- group: ""
8+
kind: Service
9+
name: foo
10+
jwtRules:
11+
- issuer: "example.com"
12+
jwksUri: https://example.com/.well-known/jwks.json
13+
forwardOriginalToken: true
14+
fromCookies: [foo]
15+
fromHeaders:
16+
- name: foo
17+
prefix: baz
18+
outputClaimToHeaders:
19+
- claim: abc
20+
header: def
21+
timeout: 5s
22+
outputPayloadToHeader: header
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
_err: "name in body should be at least 1 chars long"
2+
apiVersion: telemetry.istio.io/v1alpha1
3+
kind: Telemetry
4+
metadata:
5+
name: bad-provider
6+
spec:
7+
metrics:
8+
- providers:
9+
- name: ""
10+
---
11+
_err: "customMetric in body should be at least 1 chars lon"
12+
apiVersion: telemetry.istio.io/v1alpha1
13+
kind: Telemetry
14+
metadata:
15+
name: bad-custom-metric
16+
spec:
17+
metrics:
18+
- overrides:
19+
- match:
20+
customMetric: ""
21+
---
22+
_err: "value must be set when operation is UPSERT"
23+
apiVersion: telemetry.istio.io/v1alpha1
24+
kind: Telemetry
25+
metadata:
26+
name: bad-tag-upsert
27+
spec:
28+
metrics:
29+
- overrides:
30+
- tagOverrides:
31+
foo:
32+
operation: UPSERT
33+
---
34+
_err: "value must not be set when operation is REMOVE"
35+
apiVersion: telemetry.istio.io/v1alpha1
36+
kind: Telemetry
37+
metadata:
38+
name: bad-tag-remove
39+
spec:
40+
metrics:
41+
- overrides:
42+
- tagOverrides:
43+
foo:
44+
operation: REMOVE
45+
value: oops
46+
---
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
apiVersion: telemetry.istio.io/v1alpha1
2+
kind: Telemetry
3+
metadata:
4+
name: full
5+
spec:
6+
metrics:
7+
- providers:
8+
- name: prometheus
9+
reportingInterval: 5s
10+
overrides:
11+
- tagOverrides:
12+
request_method:
13+
value: "request.method"
14+
request_host:
15+
value: "request.host"
16+
match:
17+
customMetric: "foo"
18+
disabled: false
19+
- match:
20+
metric: GRPC_REQUEST_MESSAGES
21+
disabled: true
22+
accessLogging:
23+
- disabled: false
24+
filter:
25+
expression: 'true'
26+
match:
27+
mode: CLIENT
28+
providers:
29+
- name: stdout
30+
tracing:
31+
- providers:
32+
- name: otlp
33+
match:
34+
mode: CLIENT_AND_SERVER
35+
randomSamplingPercentage: 54.54
36+
useRequestIdForTraceSampling: true
37+
disableSpanReporting: false
38+
customTags:
39+
env:
40+
environment:
41+
name: "NAME"
42+
defaultValue: "default"
43+
header:
44+
header:
45+
name: "x-name"
46+
defaultValue: "default name"
47+
literal:
48+
literal:
49+
value: "default literal"
50+
---
51+
apiVersion: telemetry.istio.io/v1alpha1
52+
kind: Telemetry
53+
metadata:
54+
name: tag-upsert
55+
spec:
56+
metrics:
57+
- overrides:
58+
- tagOverrides:
59+
foo:
60+
operation: UPSERT
61+
value: add
62+
---
63+
apiVersion: telemetry.istio.io/v1alpha1
64+
kind: Telemetry
65+
metadata:
66+
name: tag-remove
67+
spec:
68+
metrics:
69+
- overrides:
70+
- tagOverrides:
71+
foo:
72+
operation: REMOVE
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
_err: 'spec.match[0].ports[0].number: Required value'
2+
apiVersion: extensions.istio.io/v1alpha1
3+
kind: WasmPlugin
4+
metadata:
5+
name: unset-port
6+
spec:
7+
match:
8+
- ports:
9+
- {}
10+
---
11+
_err: 'spec.url in body should be at least'
12+
apiVersion: extensions.istio.io/v1alpha1
13+
kind: WasmPlugin
14+
metadata:
15+
name: unset-url
16+
spec:
17+
url: ""
18+
---
19+
_err: 'url must have schema one of'
20+
apiVersion: extensions.istio.io/v1alpha1
21+
kind: WasmPlugin
22+
metadata:
23+
name: invalid-url
24+
spec:
25+
url: "#%blah$#@"
26+
---
27+
_err: 'url must have schema one of'
28+
apiVersion: extensions.istio.io/v1alpha1
29+
kind: WasmPlugin
30+
metadata:
31+
name: invalid-url-schema
32+
spec:
33+
url: "fake://example.com"
34+
---
35+
_err: 'spec.sha256 in body should match'
36+
apiVersion: extensions.istio.io/v1alpha1
37+
kind: WasmPlugin
38+
metadata:
39+
name: invalid-sha256
40+
spec:
41+
url: "http://test"
42+
sha256: foo
43+
---
44+
_err: 'spec.imagePullSecret in body should be at least 1 chars long'
45+
apiVersion: extensions.istio.io/v1alpha1
46+
kind: WasmPlugin
47+
metadata:
48+
name: invalid-imagePullSecret
49+
spec:
50+
url: "http://test"
51+
imagePullSecret: ""
52+
---
53+
_err: 'spec.pluginName in body should be at least 1 chars long'
54+
apiVersion: extensions.istio.io/v1alpha1
55+
kind: WasmPlugin
56+
metadata:
57+
name: invalid-pluginName
58+
spec:
59+
url: "http://test"
60+
pluginName: ""
61+
---
62+
_err: 'Duplicate value'
63+
apiVersion: extensions.istio.io/v1alpha1
64+
kind: WasmPlugin
65+
metadata:
66+
name: duplicate-env
67+
spec:
68+
url: "http://test"
69+
vmConfig:
70+
env:
71+
- name: a
72+
- name: a
73+
---
74+
_err: 'spec.vmConfig.env[0].name in body should be at least'
75+
apiVersion: extensions.istio.io/v1alpha1
76+
kind: WasmPlugin
77+
metadata:
78+
name: invalid-env-name
79+
spec:
80+
url: "http://test"
81+
vmConfig:
82+
env:
83+
- name: ""
84+
---
85+
_err: 'value may only be set when valueFrom is INLINE'
86+
apiVersion: extensions.istio.io/v1alpha1
87+
kind: WasmPlugin
88+
metadata:
89+
name: invalid-env-name
90+
spec:
91+
url: "http://test"
92+
vmConfig:
93+
env:
94+
- name: "test"
95+
valueFrom: HOST
96+
value: "value"
97+
---
98+
_err: 'value may only be set when valueFrom is INLINE'
99+
apiVersion: extensions.istio.io/v1alpha1
100+
kind: WasmPlugin
101+
metadata:
102+
name: invalid-env-name
103+
spec:
104+
url: "http://test"
105+
vmConfig:
106+
env:
107+
- name: "test"
108+
valueFrom: HOST
109+
value: "value"
110+
---
111+
_err: 'spec in body must be of type object: "null"'
112+
apiVersion: extensions.istio.io/v1alpha1
113+
kind: WasmPlugin
114+
metadata:
115+
name: nil
116+
spec:
117+
---
118+
_err: 'spec.url in body must be of type string: "null"'
119+
apiVersion: extensions.istio.io/v1alpha1
120+
kind: WasmPlugin
121+
metadata:
122+
name: nested-nil
123+
spec:
124+
url:
125+
---
126+
_err: 'wildcard not allowed in label value match'
127+
apiVersion: extensions.istio.io/v1alpha1
128+
kind: WasmPlugin
129+
metadata:
130+
name: invalid-selector
131+
spec:
132+
url: "http://test"
133+
selector:
134+
matchLabels:
135+
istio: "bar*"
136+
---
137+
_err: 'wildcard not allowed in label key match'
138+
apiVersion: extensions.istio.io/v1alpha1
139+
kind: WasmPlugin
140+
metadata:
141+
name: invalid-selector
142+
spec:
143+
url: "http://test"
144+
selector:
145+
matchLabels:
146+
"istio*": "bar"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
apiVersion: extensions.istio.io/v1alpha1
2+
kind: WasmPlugin
3+
metadata:
4+
name: full
5+
spec:
6+
selector:
7+
matchLabels:
8+
istio: ingressgateway
9+
url: file:///opt/filters/openid.wasm
10+
sha256: 1ef0c9a92b0420cf25f7fe5d481b231464bc88f486ca3b9c83ed5cc21d2f6210
11+
phase: AUTHN
12+
pluginConfig:
13+
openid_server: authn
14+
openid_realm: ingress
15+
---
16+
apiVersion: extensions.istio.io/v1alpha1
17+
kind: WasmPlugin
18+
metadata:
19+
name: sha256-empty
20+
spec:
21+
url: "http://test"
22+
---
23+
apiVersion: extensions.istio.io/v1alpha1
24+
kind: WasmPlugin
25+
metadata:
26+
name: url-without-schema
27+
spec:
28+
url: "test"
29+
---
30+
apiVersion: extensions.istio.io/v1alpha1
31+
kind: WasmPlugin
32+
metadata:
33+
name: env
34+
spec:
35+
url: "http://test"
36+
vmConfig:
37+
env:
38+
- name: "test"
39+
valueFrom: HOST
40+
- name: "test2"
41+
valueFrom: INLINE
42+
value: "test"
43+
- name: "test3"
44+
value: "test"

‎pkg/config/validation/validation.go

+48-4
Original file line numberDiff line numberDiff line change
@@ -1533,8 +1533,8 @@ var ValidatePeerAuthentication = RegisterValidateFunc("ValidatePeerAuthenticatio
15331533
}
15341534

15351535
for port := range in.PortLevelMtls {
1536-
if port == 0 {
1537-
errs = appendErrors(errs, fmt.Errorf("port cannot be 0"))
1536+
if port <= 0 || port > 65535 {
1537+
errs = appendErrors(errs, fmt.Errorf("port must be in range 1..65535"))
15381538
}
15391539
}
15401540

@@ -3066,6 +3066,8 @@ var ValidateWasmPlugin = RegisterValidateFunc("ValidateWasmPlugin",
30663066
validatePolicyTargetReferences(spec.GetTargetRefs()),
30673067
validateWasmPluginURL(spec.Url),
30683068
validateWasmPluginSHA(spec),
3069+
validateWasmPluginImagePullSecret(spec),
3070+
validateWasmPluginName(spec),
30693071
validateWasmPluginVMConfig(spec.VmConfig),
30703072
validateWasmPluginMatch(spec.Match),
30713073
)
@@ -3080,16 +3082,36 @@ func validateWasmPluginURL(pluginURL string) error {
30803082
"": true, "file": true, "http": true, "https": true, "oci": true,
30813083
}
30823084

3083-
u, err := url.Parse(pluginURL)
3085+
u, err := strictParseURL(pluginURL)
30843086
if err != nil {
3085-
return fmt.Errorf("failed to parse url: %s", err)
3087+
return err
30863088
}
30873089
if _, found := validSchemes[u.Scheme]; !found {
30883090
return fmt.Errorf("url contains unsupported scheme: %s", u.Scheme)
30893091
}
30903092
return nil
30913093
}
30923094

3095+
func strictParseURL(originalURL string) (*url.URL, error) {
3096+
u := originalURL
3097+
ur, err := url.ParseRequestURI(u)
3098+
if err != nil {
3099+
u = "http://" + originalURL
3100+
nu, nerr := url.ParseRequestURI(u)
3101+
if nerr != nil {
3102+
return nil, fmt.Errorf("failed to parse url: %s", err) // return original err
3103+
}
3104+
if _, err := url.Parse(u); err != nil {
3105+
return nil, fmt.Errorf("failed to strict parse url: %s", err)
3106+
}
3107+
return nu, nil
3108+
}
3109+
if _, err := url.Parse(u); err != nil {
3110+
return nil, fmt.Errorf("failed to strict parse url: %s", err)
3111+
}
3112+
return ur, nil
3113+
}
3114+
30933115
func validateWasmPluginSHA(plugin *extensions.WasmPlugin) error {
30943116
if plugin.Sha256 == "" {
30953117
return nil
@@ -3105,6 +3127,20 @@ func validateWasmPluginSHA(plugin *extensions.WasmPlugin) error {
31053127
return nil
31063128
}
31073129

3130+
func validateWasmPluginImagePullSecret(plugin *extensions.WasmPlugin) error {
3131+
if len(plugin.ImagePullSecret) > 253 {
3132+
return fmt.Errorf("imagePullSecret field must be less than 253 characters long")
3133+
}
3134+
return nil
3135+
}
3136+
3137+
func validateWasmPluginName(plugin *extensions.WasmPlugin) error {
3138+
if len(plugin.PluginName) > 256 {
3139+
return fmt.Errorf("pluginName field must be less than 255 characters long")
3140+
}
3141+
return nil
3142+
}
3143+
31083144
func validateWasmPluginVMConfig(vm *extensions.VmConfig) error {
31093145
if vm == nil || len(vm.Env) == 0 {
31103146
return nil
@@ -3120,9 +3156,17 @@ func validateWasmPluginVMConfig(vm *extensions.VmConfig) error {
31203156
return fmt.Errorf("spec.vmConfig.env invalid")
31213157
}
31223158

3159+
if len(env.Name) > 256 {
3160+
return fmt.Errorf("env.name field must be less than 255 characters long")
3161+
}
3162+
31233163
if keys.InsertContains(env.Name) {
31243164
return fmt.Errorf("duplicate env")
31253165
}
3166+
3167+
if env.ValueFrom != extensions.EnvValueSource_INLINE && env.Value != "" {
3168+
return fmt.Errorf("value may only be set when valueFrom is INLINE")
3169+
}
31263170
}
31273171

31283172
return nil

0 commit comments

Comments
 (0)
Please sign in to comment.