@@ -17,45 +17,72 @@ package v2_test
1717
1818import (
1919 "context"
20+ "encoding/json"
21+ "os"
22+ "path/filepath"
23+ "runtime"
2024 "testing"
2125
2226 "github.com/stretchr/testify/assert"
2327 "github.com/stretchr/testify/require"
2428 apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
29+ apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2530 structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
2631 "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel"
2732 celconfig "k8s.io/apiserver/pkg/apis/cel"
33+ sigsyaml "sigs.k8s.io/yaml"
2834)
2935
30- // jwtAuthValueSchema mirrors the CEL rule on ApisixConsumerJwtAuthValue.
31- // It must be kept in sync with the +kubebuilder:validation:XValidation marker
32- // on that type.
33- var jwtAuthValueSchema = & apiextensions.JSONSchemaProps {
34- Type : "object" ,
35- Properties : map [string ]apiextensions.JSONSchemaProps {
36- "key" : {Type : "string" },
37- "secret" : {Type : "string" },
38- "public_key" : {Type : "string" },
39- "private_key" : {Type : "string" },
40- "algorithm" : {Type : "string" },
41- "exp" : {Type : "integer" , Format : "int64" },
42- "base64_secret" : {Type : "boolean" },
43- "lifetime_grace_period" : {Type : "integer" , Format : "int64" },
44- },
45- Required : []string {"key" },
46- XValidations : []apiextensions.ValidationRule {
47- {
48- Rule : "!has(self.algorithm) || self.algorithm in ['HS256','HS384','HS512'] || (has(self.public_key) && self.public_key != '') || (has(self.private_key) && self.private_key != '')" ,
49- Message : "asymmetric JWT algorithms (RS*/ES*/PS*/EdDSA) require at least one of public_key or private_key" ,
50- },
51- },
52- }
53-
54- func validateJwtAuthValue (t * testing.T , obj map [string ]interface {}) error {
36+ // loadJwtAuthValueSchema reads the ApisixConsumer CRD YAML and extracts the
37+ // structural schema for spec.authParameter.jwtAuth.value, so that CEL tests
38+ // always validate against the real generated schema rather than a hand-written copy.
39+ func loadJwtAuthValueSchema (t * testing.T ) * structuralschema.Structural {
5540 t .Helper ()
56- structural , err := structuralschema .NewStructural (jwtAuthValueSchema )
41+
42+ _ , thisFile , _ , _ := runtime .Caller (0 )
43+ crdPath := filepath .Join (filepath .Dir (thisFile ), ".." , ".." ,
44+ "config" , "crd" , "bases" , "apisix.apache.org_apisixconsumers.yaml" )
45+
46+ data , err := os .ReadFile (crdPath )
47+ require .NoError (t , err , "failed to read CRD file: %s" , crdPath )
48+
49+ var crd apiextensionsv1.CustomResourceDefinition
50+ jsonData , err := sigsyaml .YAMLToJSON (data )
51+ require .NoError (t , err , "failed to convert CRD YAML to JSON" )
52+ err = json .Unmarshal (jsonData , & crd )
53+ require .NoError (t , err , "failed to unmarshal CRD" )
54+
55+ // Find the v2 version schema.
56+ var v1Schema * apiextensionsv1.JSONSchemaProps
57+ for _ , v := range crd .Spec .Versions {
58+ if v .Name == "v2" {
59+ v1Schema = v .Schema .OpenAPIV3Schema
60+ break
61+ }
62+ }
63+ require .NotNil (t , v1Schema , "v2 schema not found in CRD" )
64+
65+ // Navigate: spec.authParameter.jwtAuth.value
66+ jwtAuthValueV1 := v1Schema .
67+ Properties ["spec" ].
68+ Properties ["authParameter" ].
69+ Properties ["jwtAuth" ].
70+ Properties ["value" ]
71+
72+ // Convert v1 JSONSchemaProps to internal type required by NewStructural.
73+ var internal apiextensions.JSONSchemaProps
74+ err = apiextensionsv1 .Convert_v1_JSONSchemaProps_To_apiextensions_JSONSchemaProps (
75+ & jwtAuthValueV1 , & internal , nil ,
76+ )
77+ require .NoError (t , err , "failed to convert v1 schema to internal" )
78+
79+ structural , err := structuralschema .NewStructural (& internal )
5780 require .NoError (t , err , "failed to build structural schema" )
81+ return structural
82+ }
5883
84+ func validateJwtAuthValue (t * testing.T , structural * structuralschema.Structural , obj map [string ]interface {}) error {
85+ t .Helper ()
5986 celValidator := cel .NewValidator (structural , false , celconfig .PerCallLimit )
6087 errs , _ := celValidator .Validate (context .Background (), nil , structural , obj , nil , celconfig .RuntimeCELCostBudget )
6188 if len (errs ) > 0 {
@@ -67,118 +94,129 @@ func validateJwtAuthValue(t *testing.T, obj map[string]interface{}) error {
6794// TestJwtAuthCEL_SymmetricHS256WithSecret verifies that HS256 + secret
6895// without private_key passes CEL validation.
6996func TestJwtAuthCEL_SymmetricHS256WithSecret (t * testing.T ) {
97+ schema := loadJwtAuthValueSchema (t )
7098 obj := map [string ]interface {}{
7199 "key" : "my-key" ,
72100 "secret" : "my-secret" ,
73101 "algorithm" : "HS256" ,
74102 }
75- assert .NoError (t , validateJwtAuthValue (t , obj ))
103+ assert .NoError (t , validateJwtAuthValue (t , schema , obj ))
76104}
77105
78106// TestJwtAuthCEL_SymmetricHS384WithSecret verifies that HS384 + secret passes.
79107func TestJwtAuthCEL_SymmetricHS384WithSecret (t * testing.T ) {
108+ schema := loadJwtAuthValueSchema (t )
80109 obj := map [string ]interface {}{
81110 "key" : "my-key" ,
82111 "secret" : "my-secret" ,
83112 "algorithm" : "HS384" ,
84113 }
85- assert .NoError (t , validateJwtAuthValue (t , obj ))
114+ assert .NoError (t , validateJwtAuthValue (t , schema , obj ))
86115}
87116
88117// TestJwtAuthCEL_SymmetricHS512WithSecret verifies that HS512 + secret passes.
89118func TestJwtAuthCEL_SymmetricHS512WithSecret (t * testing.T ) {
119+ schema := loadJwtAuthValueSchema (t )
90120 obj := map [string ]interface {}{
91121 "key" : "my-key" ,
92122 "secret" : "my-secret" ,
93123 "algorithm" : "HS512" ,
94124 }
95- assert .NoError (t , validateJwtAuthValue (t , obj ))
125+ assert .NoError (t , validateJwtAuthValue (t , schema , obj ))
96126}
97127
98128// TestJwtAuthCEL_NoAlgorithmDefaultsToSymmetric verifies that omitting
99129// algorithm (defaults to HS256 server-side) passes CEL validation.
100130func TestJwtAuthCEL_NoAlgorithmDefaultsToSymmetric (t * testing.T ) {
131+ schema := loadJwtAuthValueSchema (t )
101132 obj := map [string ]interface {}{
102133 "key" : "my-key" ,
103134 "secret" : "my-secret" ,
104135 }
105- assert .NoError (t , validateJwtAuthValue (t , obj ))
136+ assert .NoError (t , validateJwtAuthValue (t , schema , obj ))
106137}
107138
108139// TestJwtAuthCEL_AsymmetricRS256WithPublicKey verifies that RS256 + public_key passes.
109140func TestJwtAuthCEL_AsymmetricRS256WithPublicKey (t * testing.T ) {
141+ schema := loadJwtAuthValueSchema (t )
110142 obj := map [string ]interface {}{
111143 "key" : "my-key" ,
112144 "public_key" : "-----BEGIN PUBLIC KEY-----\n MFww\n -----END PUBLIC KEY-----" ,
113145 "algorithm" : "RS256" ,
114146 }
115- assert .NoError (t , validateJwtAuthValue (t , obj ))
147+ assert .NoError (t , validateJwtAuthValue (t , schema , obj ))
116148}
117149
118150// TestJwtAuthCEL_AsymmetricRS256WithPrivateKey verifies that RS256 + private_key passes
119- // (backward compatibility: existing configurations only have private_key).
151+ // (backward compatibility: existing configurations may only have private_key).
120152func TestJwtAuthCEL_AsymmetricRS256WithPrivateKey (t * testing.T ) {
153+ schema := loadJwtAuthValueSchema (t )
121154 obj := map [string ]interface {}{
122155 "key" : "my-key" ,
123156 "private_key" : "-----BEGIN RSA PRIVATE KEY-----\n MIIE\n -----END RSA PRIVATE KEY-----" ,
124157 "algorithm" : "RS256" ,
125158 }
126- assert .NoError (t , validateJwtAuthValue (t , obj ))
159+ assert .NoError (t , validateJwtAuthValue (t , schema , obj ))
127160}
128161
129162// TestJwtAuthCEL_AsymmetricRS256WithBothKeys verifies that RS256 + both keys passes.
130163func TestJwtAuthCEL_AsymmetricRS256WithBothKeys (t * testing.T ) {
164+ schema := loadJwtAuthValueSchema (t )
131165 obj := map [string ]interface {}{
132166 "key" : "my-key" ,
133167 "public_key" : "-----BEGIN PUBLIC KEY-----\n MFww\n -----END PUBLIC KEY-----" ,
134168 "private_key" : "-----BEGIN RSA PRIVATE KEY-----\n MIIE\n -----END RSA PRIVATE KEY-----" ,
135169 "algorithm" : "RS256" ,
136170 }
137- assert .NoError (t , validateJwtAuthValue (t , obj ))
171+ assert .NoError (t , validateJwtAuthValue (t , schema , obj ))
138172}
139173
140174// TestJwtAuthCEL_AsymmetricRS256WithoutAnyKey verifies that RS256 without
141175// any key is rejected by CEL validation.
142176func TestJwtAuthCEL_AsymmetricRS256WithoutAnyKey (t * testing.T ) {
177+ schema := loadJwtAuthValueSchema (t )
143178 obj := map [string ]interface {}{
144179 "key" : "my-key" ,
145180 "algorithm" : "RS256" ,
146181 }
147- err := validateJwtAuthValue (t , obj )
182+ err := validateJwtAuthValue (t , schema , obj )
148183 assert .Error (t , err , "RS256 without public_key or private_key should be rejected" )
149184 assert .Contains (t , err .Error (), "asymmetric JWT algorithms" )
150185}
151186
152187// TestJwtAuthCEL_AsymmetricES256WithoutAnyKey verifies that ES256 without
153188// any key is rejected.
154189func TestJwtAuthCEL_AsymmetricES256WithoutAnyKey (t * testing.T ) {
190+ schema := loadJwtAuthValueSchema (t )
155191 obj := map [string ]interface {}{
156192 "key" : "my-key" ,
157193 "algorithm" : "ES256" ,
158194 }
159- err := validateJwtAuthValue (t , obj )
195+ err := validateJwtAuthValue (t , schema , obj )
160196 assert .Error (t , err , "ES256 without public_key or private_key should be rejected" )
161197}
162198
163199// TestJwtAuthCEL_AsymmetricEdDSAWithoutAnyKey verifies that EdDSA without
164200// any key is rejected.
165201func TestJwtAuthCEL_AsymmetricEdDSAWithoutAnyKey (t * testing.T ) {
202+ schema := loadJwtAuthValueSchema (t )
166203 obj := map [string ]interface {}{
167204 "key" : "my-key" ,
168205 "algorithm" : "EdDSA" ,
169206 }
170- err := validateJwtAuthValue (t , obj )
207+ err := validateJwtAuthValue (t , schema , obj )
171208 assert .Error (t , err , "EdDSA without public_key or private_key should be rejected" )
172209}
173210
174211// TestJwtAuthCEL_AsymmetricWithEmptyPublicKey verifies that an asymmetric
175212// algorithm with an empty public_key string is rejected (same as absent).
176213func TestJwtAuthCEL_AsymmetricWithEmptyPublicKey (t * testing.T ) {
214+ schema := loadJwtAuthValueSchema (t )
177215 obj := map [string ]interface {}{
178216 "key" : "my-key" ,
179217 "public_key" : "" ,
180218 "algorithm" : "RS256" ,
181219 }
182- err := validateJwtAuthValue (t , obj )
220+ err := validateJwtAuthValue (t , schema , obj )
183221 assert .Error (t , err , "RS256 with empty public_key should be rejected" )
184222}
0 commit comments