@@ -71,6 +71,7 @@ type ptp4lConf struct {
7171 sections map [string ]ptp4lConfSection
7272}
7373
74+ // +kubebuilder:object:generate=false
7475// Ptp4lConf is a public wrapper for ptp4lConf
7576type Ptp4lConf struct {
7677 conf ptp4lConf
@@ -106,7 +107,7 @@ func (output *ptp4lConf) populatePtp4lConf(config *string, ptp4lopts *string) er
106107 currentLine := strings .Split (line , "]" )
107108
108109 if len (currentLine ) < 2 {
109- return errors .New ("Section missing closing ']'" )
110+ return errors .New ("section missing closing ']'" )
110111 }
111112
112113 currentSection = fmt .Sprintf ("%s]" , currentLine [0 ])
@@ -120,7 +121,7 @@ func (output *ptp4lConf) populatePtp4lConf(config *string, ptp4lopts *string) er
120121 output .sections [currentSection ] = section
121122 }
122123 } else {
123- return errors .New ("Config option not in section" )
124+ return errors .New ("config option not in section" )
124125 }
125126 }
126127 _ , exist := output .sections ["[global]" ]
@@ -221,45 +222,57 @@ func (r *PtpConfig) validate() error {
221222 }
222223 }
223224 }
225+
226+ // Validate secret-related settings for this profile
227+ if profile .PtpSecretName != nil && * profile .PtpSecretName != "" {
228+ // Check that secret exists
229+ if err := r .validateSecretExistsForProfile (context .Background (), profile ); err != nil {
230+ return err
231+ }
232+
233+ // Check for conflicts with other PtpConfigs
234+ if err := r .validateSecretConflictsForProfile (context .Background (), profile ); err != nil {
235+ return err
236+ }
237+ }
224238 }
225239 return nil
226240}
227241
228- // validateSecretConflicts checks if this PtpConfig 's sa_file + secret combination
242+ // validateSecretConflictsForProfile checks if a single profile 's sa_file + secret combination
229243// conflicts with any existing PtpConfigs in the openshift-ptp namespace
230- func (r * PtpConfig ) validateSecretConflicts (ctx context.Context ) error {
244+ func (r * PtpConfig ) validateSecretConflictsForProfile (ctx context.Context , profile PtpProfile ) error {
231245 if webhookClient == nil {
232246 ptpconfiglog .Info ("webhook client not initialized, skipping cross-PtpConfig validation" )
233247 return nil
234248 }
235249
236- // Build map of sa_file -> secret for THIS PtpConfig
237- currentSaFileToSecret := make (map [string ]string )
238- for _ , profile := range r .Spec .Profile {
239- if profile .PtpSecretName == nil || * profile .PtpSecretName == "" {
240- continue
241- }
242- if profile .Ptp4lConf == nil {
243- continue
244- }
245-
246- conf := & ptp4lConf {}
247- if err := conf .populatePtp4lConf (profile .Ptp4lConf , profile .Ptp4lOpts ); err != nil {
248- continue
249- }
250+ // Skip if profile doesn't use secrets
251+ if profile .PtpSecretName == nil || * profile .PtpSecretName == "" {
252+ return nil
253+ }
254+ if profile .Ptp4lConf == nil {
255+ return nil
256+ }
250257
251- if globalSection , exists := conf .sections ["[global]" ]; exists {
252- if saFile , exists := globalSection .options ["sa_file" ]; exists && saFile != "" {
253- currentSaFileToSecret [saFile ] = * profile .PtpSecretName
254- }
255- }
258+ // Get sa_file for THIS profile
259+ conf := & ptp4lConf {}
260+ if err := conf .populatePtp4lConf (profile .Ptp4lConf , profile .Ptp4lOpts ); err != nil {
261+ return nil // Skip if can't parse
256262 }
257263
258- // If this PtpConfig doesn't use any secrets, no conflict possible
259- if len ( currentSaFileToSecret ) == 0 {
264+ globalSection , exists := conf . sections [ "[global]" ]
265+ if ! exists {
260266 return nil
261267 }
262268
269+ saFile , exists := globalSection .options ["sa_file" ]
270+ if ! exists || saFile == "" {
271+ return nil // No sa_file, no conflict possible
272+ }
273+
274+ currentSecret := * profile .PtpSecretName
275+
263276 // List all existing PtpConfigs in openshift-ptp namespace
264277 ptpConfigList := & PtpConfigList {}
265278 if err := webhookClient .List (ctx , ptpConfigList , & client.ListOptions {
@@ -270,35 +283,35 @@ func (r *PtpConfig) validateSecretConflicts(ctx context.Context) error {
270283 return nil
271284 }
272285
273- // Check each existing PtpConfig
286+ // Check each existing PtpConfig for conflicts
274287 for _ , existingConfig := range ptpConfigList .Items {
275288 // Skip checking against ourselves (for updates)
276289 if existingConfig .Name == r .Name && existingConfig .Namespace == r .Namespace {
277290 continue
278291 }
279292
280293 // Check each profile in the existing config
281- for _ , profile := range existingConfig .Spec .Profile {
282- if profile .PtpSecretName == nil || * profile .PtpSecretName == "" {
294+ for _ , existingProfile := range existingConfig .Spec .Profile {
295+ if existingProfile .PtpSecretName == nil || * existingProfile .PtpSecretName == "" {
283296 continue
284297 }
285- if profile .Ptp4lConf == nil {
298+ if existingProfile .Ptp4lConf == nil {
286299 continue
287300 }
288301
289- conf := & ptp4lConf {}
290- if err := conf .populatePtp4lConf (profile .Ptp4lConf , profile .Ptp4lOpts ); err != nil {
302+ existingConf := & ptp4lConf {}
303+ if err := existingConf .populatePtp4lConf (existingProfile .Ptp4lConf , existingProfile .Ptp4lOpts ); err != nil {
291304 continue
292305 }
293306
294- if globalSection , exists := conf .sections ["[global]" ]; exists {
295- if saFile , exists := globalSection .options ["sa_file" ]; exists && saFile != "" {
296- // Check if THIS PtpConfig uses the same sa_file
297- if currentSecret , found := currentSaFileToSecret [ saFile ]; found {
298- // Conflict: same sa_file but different secret
299- if currentSecret != * profile . PtpSecretName {
300- return fmt .Errorf ("sa_file '%s' conflict: PtpConfig '%s' already uses secret '%s' with this sa_file path, but this PtpConfig tries to use secret '%s'. All PtpConfigs using the same sa_file must reference the same secret" ,
301- saFile , existingConfig .Name , * profile .PtpSecretName , currentSecret )
307+ if globalSection , exists := existingConf .sections ["[global]" ]; exists {
308+ if existingSaFile , exists := globalSection .options ["sa_file" ]; exists && existingSaFile != "" {
309+ // Check if same sa_file as our profile
310+ if existingSaFile == saFile {
311+ // Same sa_file - check if different secret
312+ if * existingProfile . PtpSecretName != currentSecret {
313+ return fmt .Errorf ("sa_file '%s' conflict: PtpConfig '%s' already uses secret '%s' with this sa_file path, but this profile tries to use secret '%s'. All PtpConfigs using the same sa_file must reference the same secret" ,
314+ saFile , existingConfig .Name , * existingProfile .PtpSecretName , currentSecret )
302315 }
303316 }
304317 }
@@ -309,55 +322,54 @@ func (r *PtpConfig) validateSecretConflicts(ctx context.Context) error {
309322 return nil
310323}
311324
312- // validateSecretExists checks that ptpSecretName references an existing secret
313- func (r * PtpConfig ) validateSecretExists (ctx context.Context ) error {
325+ // validateSecretExistsForProfile checks that a single profile's ptpSecretName references an existing secret
326+ func (r * PtpConfig ) validateSecretExistsForProfile (ctx context.Context , profile PtpProfile ) error {
314327 if webhookClient == nil {
315328 ptpconfiglog .Info ("webhook client not initialized, skipping secret existence validation" )
316329 return nil
317330 }
318331
319- for _ , profile := range r .Spec .Profile {
320- // Skip if no secret specified
321- if profile .PtpSecretName == nil || * profile .PtpSecretName == "" {
322- continue
323- }
332+ // Skip if no secret specified
333+ if profile .PtpSecretName == nil || * profile .PtpSecretName == "" {
334+ return nil
335+ }
324336
325- secretName := * profile .PtpSecretName
326- profileName := "unknown"
327- if profile .Name != nil {
328- profileName = * profile .Name
329- }
337+ secretName := * profile .PtpSecretName
338+ profileName := "unknown"
339+ if profile .Name != nil {
340+ profileName = * profile .Name
341+ }
330342
331- // Try to get the secret from openshift-ptp namespace
332- secret := & corev1.Secret {}
333- err := webhookClient .Get (ctx , types.NamespacedName {
334- Namespace : "openshift-ptp" ,
335- Name : secretName ,
336- }, secret )
337-
338- if err != nil {
339- if apierrors .IsNotFound (err ) {
340- return fmt .Errorf ("secret '%s' referenced by profile '%s' does not exist in namespace 'openshift-ptp'. Please create the secret before referencing it in PtpConfig" ,
341- secretName , profileName )
342- }
343- // For other errors (like permission issues), log but don't block
344- ptpconfiglog .Error (err , "failed to verify secret existence" , "secret" , secretName , "profile" , profileName )
345- // Fail open - don't block if we can't verify
346- return nil
343+ // Try to get the secret from openshift-ptp namespace
344+ secret := & corev1.Secret {}
345+ err := webhookClient .Get (ctx , types.NamespacedName {
346+ Namespace : "openshift-ptp" ,
347+ Name : secretName ,
348+ }, secret )
349+
350+ if err != nil {
351+ if apierrors .IsNotFound (err ) {
352+ return fmt .Errorf ("secret '%s' referenced by profile '%s' does not exist in namespace 'openshift-ptp'. Please create the secret before referencing it in PtpConfig" ,
353+ secretName , profileName )
347354 }
355+ // For other errors (like permission issues), log but don't block
356+ ptpconfiglog .Error (err , "failed to verify secret existence" , "secret" , secretName , "profile" , profileName )
357+ // Fail open - don't block if we can't verify
358+ return nil
359+ }
348360
349- ptpconfiglog .Info ("validated secret exists" , "secret" , secretName , "profile" , profileName )
361+ ptpconfiglog .Info ("validated secret exists" , "secret" , secretName , "profile" , profileName )
350362
351- // Validate SPP (Security Parameter Profile) if specified
352- if err := r .validateSppInSecret (profile , secret ); err != nil {
353- return err
354- }
363+ // Validate SPP (Security Parameter Profile) if specified
364+ if err := r .validateSppInSecret (profile , secret ); err != nil {
365+ return err
355366 }
356367
357368 return nil
358369}
359370
360371// validateSppInSecret checks that the spp value in ptp4lConf exists in the referenced secret
372+ // Combines parsing and validation in one pass for efficiency
361373func (r * PtpConfig ) validateSppInSecret (profile PtpProfile , secret * corev1.Secret ) error {
362374 // Skip if no ptp4lConf
363375 if profile .Ptp4lConf == nil {
@@ -387,36 +399,15 @@ func (r *PtpConfig) validateSppInSecret(profile PtpProfile, secret *corev1.Secre
387399 profileName = * profile .Name
388400 }
389401
390- // Parse the secret data to find valid SPP values
391- validSpps , err := parseValidSppsFromSecret (secret )
392- if err != nil {
393- ptpconfiglog .Error (err , "failed to parse SPPs from secret" , "secret" , secret .Name , "profile" , profileName )
394- // Fail open - don't block if we can't parse
395- return nil
396- }
397-
398- // Check if the specified SPP exists in the secret
399- if ! contains (validSpps , sppValue ) {
400- return fmt .Errorf ("spp '%s' in profile '%s' is not defined in secret '%s'. Valid SPPs in secret: %v" ,
401- sppValue , profileName , secret .Name , validSpps )
402- }
403-
404- ptpconfiglog .Info ("validated spp exists in secret" , "spp" , sppValue , "secret" , secret .Name , "profile" , profileName )
405- return nil
406- }
407-
408- // parseValidSppsFromSecret extracts all valid SPP numbers from a PTP security secret
409- // It looks for any line starting with "spp <number>" regardless of structure or sections
410- func parseValidSppsFromSecret (secret * corev1.Secret ) ([]string , error ) {
411- var validSpps []string
402+ // Validate SPP exists in secret by iterating through secret content
403+ // Early return on match for efficiency
412404
413405 // Iterate through all keys in the secret
414406 for key , value := range secret .Data {
415- // Parse the security configuration
416407 content := string (value )
417408 lines := strings .Split (content , "\n " )
418409
419- // Look for any line starting with "spp <number>"
410+ // Look for lines starting with "spp <number>"
420411 for _ , line := range lines {
421412 line = strings .TrimSpace (line )
422413
@@ -425,74 +416,47 @@ func parseValidSppsFromSecret(secret *corev1.Secret) ([]string, error) {
425416 continue
426417 }
427418
428- // Check if line starts with "spp " (case-insensitive)
419+ // Check if line starts with "spp "
429420 if strings .HasPrefix (strings .ToLower (line ), "spp " ) {
430421 parts := strings .Fields (line )
431422 if len (parts ) >= 2 {
432- sppNumber := parts [1 ]
433- // Add if not already in list
434- if ! contains (validSpps , sppNumber ) {
435- validSpps = append (validSpps , sppNumber )
423+ secretSppValue := parts [1 ]
424+
425+ // Check if this matches the PtpConfig's spp
426+ if secretSppValue == sppValue {
427+ ptpconfiglog .Info ("validated spp match" , "spp" , sppValue , "secret" , secret .Name , "profile" , profileName , "key" , key )
428+ return nil // Early return - validation passed ✅
436429 }
437430 }
438431 }
439432 }
440-
441- ptpconfiglog .Info ("parsed SPPs from secret" , "key" , key , "spps" , validSpps )
442433 }
443434
444- return validSpps , nil
445- }
446-
447- // contains checks if a string slice contains a specific string
448- func contains (slice []string , item string ) bool {
449- for _ , s := range slice {
450- if s == item {
451- return true
452- }
453- }
454- return false
435+ return fmt .Errorf ("spp '%s' in profile '%s' is not found in secret '%s' " ,
436+ sppValue , profileName , secret .Name )
455437}
456438
457439var _ webhook.Validator = & PtpConfig {}
458440
459441// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
460442func (r * PtpConfig ) ValidateCreate () (admission.Warnings , error ) {
461443 ptpconfiglog .Info ("validate create" , "name" , r .Name )
444+ // validate() now includes secret validation for each profile
462445 if err := r .validate (); err != nil {
463446 return admission.Warnings {}, err
464447 }
465448
466- // Check that referenced secrets exist
467- if err := r .validateSecretExists (context .Background ()); err != nil {
468- return admission.Warnings {}, err
469- }
470-
471- // Check for cross-PtpConfig secret conflicts
472- if err := r .validateSecretConflicts (context .Background ()); err != nil {
473- return admission.Warnings {}, err
474- }
475-
476449 return admission.Warnings {}, nil
477450}
478451
479452// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
480453func (r * PtpConfig ) ValidateUpdate (old runtime.Object ) (admission.Warnings , error ) {
481454 ptpconfiglog .Info ("validate update" , "name" , r .Name )
455+ // validate() now includes secret validation for each profile
482456 if err := r .validate (); err != nil {
483457 return admission.Warnings {}, err
484458 }
485459
486- // Check that referenced secrets exist
487- if err := r .validateSecretExists (context .Background ()); err != nil {
488- return admission.Warnings {}, err
489- }
490-
491- // Check for cross-PtpConfig secret conflicts
492- if err := r .validateSecretConflicts (context .Background ()); err != nil {
493- return admission.Warnings {}, err
494- }
495-
496460 return admission.Warnings {}, nil
497461}
498462
0 commit comments