@@ -11,6 +11,7 @@ import (
11
11
"strings"
12
12
13
13
operatorv1 "github.com/openshift/api/operator/v1"
14
+ installer "github.com/openshift/installer/pkg/types"
14
15
"github.com/openshift/managed-cluster-validating-webhooks/pkg/k8sutil"
15
16
"github.com/openshift/managed-cluster-validating-webhooks/pkg/webhooks/utils"
16
17
"github.com/pkg/errors"
@@ -54,24 +55,11 @@ var (
54
55
)
55
56
56
57
type IngressControllerWebhook struct {
57
- s runtime.Scheme
58
- kubeClient client.Client
58
+ s runtime.Scheme
59
59
// Allow caching install config and machineCIDR values...
60
- machineCIDRIP net.IP
61
60
machineCIDRNet * net.IPNet
62
61
}
63
62
64
- // installConfig struct to load the min values needed from the install-config data.
65
- // Alternative would be to vendor all of github.com/openshift/installer/pkg/types to import the proper struct.
66
- // Required values: machineCidr info, anything else we gather from this? hcp vs classic, privatelink info, etc?
67
- type installConfig struct {
68
- metav1.TypeMeta `json:",inline"`
69
- metav1.ObjectMeta `json:"metadata"`
70
- Networking struct {
71
- MachineCIDR string `json:"machineCIDR"`
72
- } `json:"networking"`
73
- }
74
-
75
63
// ObjectSelector implements Webhook interface
76
64
func (wh * IngressControllerWebhook ) ObjectSelector () * metav1.LabelSelector { return nil }
77
65
@@ -178,12 +166,10 @@ func (wh *IngressControllerWebhook) authorized(request admissionctl.Request) adm
178
166
179
167
/* TODO:
180
168
* - HypershiftEnabled is currently set to false/disabled.
181
- * If HCP is to be enabled for allowed source ranges, should this part of a 2nd ingress validator to
182
- * allow separation of validations between cluster install types? ...or if there's a reliable run time method available
183
- * to this validator to determine classic vs hcp they can remain in the single webhook(?)
184
169
* - Classic vs HCP could likely share some of the network funcions, but will need slightly
185
- * different logic as well as permissions fetching the network config info from different
186
- * source (configmap) locations and config formats(?).
170
+ * different logic for the different minimum CIDR sets required, as well as different
171
+ * permissions fetching the network config info from different
172
+ * source (configmap) locations and config formats(?).
187
173
*/
188
174
// Only check for machine cidr in allowed ranges if creating or updating resource...
189
175
reqOp := request .AdmissionRequest .Operation
@@ -204,58 +190,46 @@ func (wh *IngressControllerWebhook) authorized(request admissionctl.Request) adm
204
190
return ret
205
191
}
206
192
207
- func (wh * IngressControllerWebhook ) getMachineCIDR () (net.IP , * net.IPNet , error ) {
208
- if wh .machineCIDRIP == nil || wh .machineCIDRNet == nil {
209
- instConf , err := wh .getClusterConfig ()
210
- if err != nil {
211
- log .Error (err , "Failed to fetch machineCIDR" , "namespace" , installConfigNamespace , "configmap" , installConfigMap )
212
- return nil , nil , err
213
- }
193
+ func (wh * IngressControllerWebhook ) getMachineCIDR (instConf * installer.InstallConfig ) (* net.IPNet , error ) {
194
+ if wh .machineCIDRNet == nil {
214
195
if instConf == nil {
215
196
err := fmt .Errorf ("can not fetch machineCIDR from empty '%s' install config" , installConfigMap )
216
197
log .Error (err , "getMachineCIDR failed to find CIDR value" )
217
- return nil , nil , err
218
- }
219
- if len (instConf .Networking .MachineCIDR ) <= 0 {
220
- err := fmt .Errorf ("empty machineCIDR string value parsed from '%s' install config" , installConfigMap )
221
- log .Error (err , "getMachineCIDR found empty CIDR value" )
222
- return nil , nil , err
198
+ return nil , err
223
199
}
224
- machIP , machNet , err := net . ParseCIDR ( string ( instConf .Networking .MachineCIDR ))
225
- if err != nil {
226
- log .Error (err , "err parsing machineCIDR into network cidr" , "machineCIDR" , string ( instConf . Networking . MachineCIDR ) )
227
- return nil , nil , err
200
+ if instConf .Networking .MachineCIDR == nil {
201
+ err := fmt . Errorf ( " nil installConfig.machineCIDR value found" )
202
+ log .Error (err , "nil installConfig. machineCIDR value found" )
203
+ return nil , err
228
204
}
229
- if machIP == nil || machNet == nil {
230
- err := fmt .Errorf ("failed to parse machineCIDR string: '%s' into network structures " , string ( instConf . Networking . MachineCIDR ) )
231
- log .Error (err , "failed to parse install-config machineCIDR " )
232
- return nil , nil , err
205
+ if len ( instConf . Networking . MachineCIDR . Network ()) <= 0 || len ( instConf . Networking . MachineCIDR . IPNet . Network ()) <= 0 {
206
+ err := fmt .Errorf ("empty machineCIDR network() value parsed from '%s' install config " , installConfigMap )
207
+ log .Error (err , "getMachineCIDR found empty network value " )
208
+ return nil , err
233
209
}
234
210
// Successfully fetched, parsed, and converted the machineCIDR string into net structures...
235
- wh .machineCIDRIP = machIP
236
- wh .machineCIDRNet = machNet
211
+ wh .machineCIDRNet = & instConf .Networking .MachineCIDR .IPNet
237
212
}
238
- return wh .machineCIDRIP , wh . machineCIDRNet , nil
213
+ return wh .machineCIDRNet , nil
239
214
}
240
215
241
216
/* Fetch the install-config from the kube-system config map's data.
242
217
* this requires proper role, rolebinding for this service account's get() request
243
218
* to succeed. (see toplevel selectorsyncset). This config should not change during runtime so
244
- * this operation should cache this if possible.
219
+ * this operation should cache the value(s) if possible.
245
220
* TODO: Should it retry fetching the config if there are any failures/errors encountered while
246
221
* parsing out the the desired values?
247
222
*/
248
- func (wh * IngressControllerWebhook ) getClusterConfig () (* installConfig , error ) {
223
+ func (wh * IngressControllerWebhook ) getClusterConfig () (* installer. InstallConfig , error ) {
249
224
var err error
250
- if wh .kubeClient == nil {
251
- wh .kubeClient , err = k8sutil .KubeClient (& wh .s )
252
- if err != nil {
253
- log .Error (err , "Fail creating KubeClient for IngressControllerWebhook" )
254
- return nil , err
255
- }
225
+
226
+ kubeClient , err := k8sutil .KubeClient (& wh .s )
227
+ if err != nil {
228
+ log .Error (err , "Fail creating KubeClient for IngressControllerWebhook" )
229
+ return nil , err
256
230
}
257
231
clusterConfig := & corev1.ConfigMap {}
258
- err = wh . kubeClient .Get (context .Background (), client.ObjectKey {Name : installConfigMap , Namespace : installConfigNamespace }, clusterConfig )
232
+ err = kubeClient .Get (context .Background (), client.ObjectKey {Name : installConfigMap , Namespace : installConfigNamespace }, clusterConfig )
259
233
if err != nil {
260
234
log .Error (err , "Failed to fetch configmap: 'cluster-config-v1' for cluster config" )
261
235
return nil , err
@@ -264,8 +238,7 @@ func (wh *IngressControllerWebhook) getClusterConfig() (*installConfig, error) {
264
238
if ! ok {
265
239
return nil , fmt .Errorf ("did not find key %s in configmap %s/%s" , installConfigKeyName , installConfigNamespace , installConfigMap )
266
240
}
267
- instConf := & installConfig {}
268
-
241
+ instConf := & installer.InstallConfig {}
269
242
decoder := yaml .NewYAMLOrJSONDecoder (bytes .NewReader ([]byte (data )), 4096 )
270
243
if err := decoder .Decode (instConf ); err != nil {
271
244
return nil , errors .Wrap (err , "failed to decode install config" )
@@ -280,16 +253,12 @@ func (wh *IngressControllerWebhook) checkAllowsMachineCIDR(ipRanges []operatorv1
280
253
// where the IGC will remaining in progressing state indefinitely.
281
254
// For now return Allowed, but with a warning?
282
255
if ipRanges == nil || len (ipRanges ) <= 0 {
283
- return admissionctl .Allowed ("Allowing empty 'AllowedSourceRanges'" )
284
- }
285
- machIP , machNet , err := wh .getMachineCIDR ()
286
- if err != nil {
287
- // This represents a fault in either the webhook itself, webhook permissions, or install config.
288
- // Might be nice to have an env var etc we can set to allow proceeding w/o the immediate need to roll new code?
289
- return admissionctl .Errored (http .StatusInternalServerError , err )
256
+ return admissionctl .Allowed ("Allowing empty 'AllowedSourceRanges'." )
290
257
}
291
- machNetSize , machNetBits := machNet .Mask .Size ()
292
- log .Info ("Checking AllowedSourceRanges" , "MachineCIDR" , fmt .Sprintf ("%s/%d" , machIP .String (), machNetSize ), "NetBits" , machNetBits , "AllowedSourceRanges" , ipRanges )
258
+
259
+ machNetSize , machNetBits := wh .machineCIDRNet .Mask .Size ()
260
+ machineCIDRIP := wh .machineCIDRNet .IP
261
+ log .Info ("Checking AllowedSourceRanges" , "MachineCIDR" , fmt .Sprintf ("%s/%d" , machineCIDRIP .String (), machNetSize ), "NetBits" , machNetBits , "AllowedSourceRanges" , ipRanges )
293
262
for _ , OpV1CIDR := range ipRanges {
294
263
// Clean up the operatorV1.CIDR value into trimmed CIDR 'a.b.c.d/x' string
295
264
ASRstring := strings .TrimSpace (string (OpV1CIDR ))
@@ -304,20 +273,19 @@ func (wh *IngressControllerWebhook) checkAllowsMachineCIDR(ipRanges []operatorv1
304
273
return admissionctl .Errored (http .StatusBadRequest , fmt .Errorf ("failed to parse AllowedSourceRanges value: '%s'. Err: %s" , string (ASRstring ), err ))
305
274
}
306
275
// First check if this AlloweSourceRange entry network contains the machine cidr ip...
307
- if ! ASRNet .Contains (machIP ) {
308
- log .Info (fmt .Sprintf ("AllowedSourceRange:'%s' does not contain machine CIDR:'%s/%d'" , ASRstring , machIP .String (), machNetSize ))
276
+ if ! ASRNet .Contains (machineCIDRIP ) {
277
+ // log.Info(fmt.Sprintf("AllowedSourceRange:'%s' does not contain machine CIDR:'%s/%d'", ASRstring, machineCIDRIP .String(), machNetSize))
309
278
continue
310
279
}
311
280
// Check if this AlloweSourceRange entry mask includes the network.
312
281
ASRNetSize , ASRNetBits := ASRNet .Mask .Size ()
313
282
if machNetBits == ASRNetBits && ASRNetSize <= machNetSize {
314
- log .Info (fmt .Sprintf ("Found machineCidr:'%s/%d' within AllowedSourceRange:'%s'" , machIP .String (), machNetSize , ASRstring ))
315
- return admissionctl .Allowed (fmt .Sprintf ("Found machineCidr:'%s/%d' within AllowedSourceRange:'%s'" , machIP .String (), machNetSize , ASRstring ))
316
- //return admissionctl.Allowed("IngressController operation is allowed. Minimum AllowedSourceRanges are met.")
283
+ log .Info (fmt .Sprintf ("Found machineCidr:'%s/%d' within AllowedSourceRange:'%s'" , machineCIDRIP .String (), machNetSize , ASRstring ))
284
+ return admissionctl .Allowed (fmt .Sprintf ("Found machineCidr:'%s/%d' within AllowedSourceRange:'%s'" , machineCIDRIP .String (), machNetSize , ASRstring ))
317
285
}
318
286
}
319
- log .Info (fmt .Sprintf ("machineCidr:'%s/%d' not found within networks provided by AllowedSourceRanges:'%v'" , machIP .String (), machNetSize , ipRanges ))
320
- return admissionctl .Denied (fmt .Sprintf ("At least one AllowedSourceRange must allow machine cidr:'%s/%d'" , machIP .String (), machNetSize ))
287
+ log .Info (fmt .Sprintf ("machineCidr:'%s/%d' not found within networks provided by AllowedSourceRanges:'%v'" , machineCIDRIP .String (), machNetSize , ipRanges ))
288
+ return admissionctl .Denied (fmt .Sprintf ("At least one AllowedSourceRange must allow machine cidr:'%s/%d'" , machineCIDRIP .String (), machNetSize ))
321
289
}
322
290
323
291
// isAllowedUser checks if the user is allowed to perform the action
@@ -362,19 +330,48 @@ func (s *IngressControllerWebhook) ClassicEnabled() bool { return true }
362
330
func (s * IngressControllerWebhook ) HypershiftEnabled () bool { return false }
363
331
364
332
// NewWebhook creates a new webhook
365
- func NewWebhook () * IngressControllerWebhook {
333
+ // Allow variadic args so unit tests can provide optional test values...
334
+ func NewWebhook (params ... interface {}) * IngressControllerWebhook {
366
335
scheme := runtime .NewScheme ()
367
336
err := corev1 .AddToScheme (scheme )
368
337
if err != nil {
369
338
log .Error (err , "Fail adding corev1 scheme to IngressControllerWebhook" )
370
339
os .Exit (1 )
371
340
}
372
341
wh := & IngressControllerWebhook {
373
- s : * scheme ,
374
- kubeClient : nil ,
342
+ s : * scheme ,
343
+ }
344
+ // utils.TestHooks maps to cli flag 'testhooks' and is used during 'make test' to "test webhook URI uniqueness".
345
+ // 'make test' does not require this hook to build runtime clients/config at this time...
346
+ if utils .TestHooks {
347
+ return wh
348
+ }
349
+
350
+ if len (params ) > 0 {
351
+ param := params [0 ]
352
+ // As of know only *IPNet values can be provided by unit tests to set machineCIDR, normal webhook factory
353
+ // calls NewWebhook() without arguments...
354
+ if cidr , ok := param .(* net.IPNet ); ok {
355
+ log .Info (fmt .Sprintf ("Got test net.IPNet param network() for machineCIDR:'%s'\n " , cidr .Network ()))
356
+ wh .machineCIDRNet = cidr
357
+ } else {
358
+ log .Error (fmt .Errorf ("invalid test param provided, expected *net.IPNet machineCIDR value" ), "invalid test param provided, expected *net.IPNet machineCIDR value" )
359
+ os .Exit (1 )
360
+ }
361
+ } else {
362
+ // This is not a test run.
363
+ // Try to populate machine cidr at init. Exit with error if this fails...
364
+ instConf , err := wh .getClusterConfig ()
365
+ if err != nil {
366
+ log .Error (err , "Failed to fetch configmap for machineCIDR" , "namespace" , installConfigNamespace , "configmap" , installConfigMap )
367
+ os .Exit (1 )
368
+ }
369
+
370
+ _ , err = wh .getMachineCIDR (instConf )
371
+ if err != nil || wh .machineCIDRNet == nil {
372
+ log .Error (err , "Failed to fetch cluster machineCIDR." )
373
+ os .Exit (1 )
374
+ }
375
375
}
376
- // Try to populate machine cidr at init. If this fails it will try again on the
377
- // first update/create request involving the default ingress controller.
378
- wh .getMachineCIDR ()
379
376
return wh
380
377
}
0 commit comments